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>
|
@ -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:
|
||||
|
|
|
@ -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 | ✅ |
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export class Rocketchat {}
|
||||
export const settings = {};
|
|
@ -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]
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 35 KiB |
|
@ -1 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="primary_dark">#660B0B0B</color> </resources>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
After Width: | Height: | Size: 62 KiB |
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 987 B |
|
@ -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>
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 177 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 246 KiB |
Before Width: | Height: | Size: 4.5 KiB |
|
@ -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>
|
|
@ -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>
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 22 KiB |
|
@ -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>
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -47,7 +47,8 @@ export default StyleSheet.create({
|
|||
},
|
||||
categoryEmoji: {
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
color: '#ffffff'
|
||||
},
|
||||
customCategoryEmoji: {
|
||||
margin: 8
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
static defaultProps = {
|
||||
separator: true
|
||||
}
|
||||
|
||||
state = {
|
||||
collapsed: true,
|
||||
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
|
||||
};
|
||||
const { Site_Name } = this.props;
|
||||
this.setTitle(Site_Name);
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{Object.values(services).map(service => this.renderItem(service))}
|
||||
{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)));
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
|
@ -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'
|
||||
}
|
||||
});
|
|
@ -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} />
|
||||
</>
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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}
|
||||
</>
|
||||
|
|
|
@ -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: () => {}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}>
|
||||
<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';
|
||||
|
||||
|
|
|
@ -61,7 +61,8 @@ export default StyleSheet.create({
|
|||
},
|
||||
reactionEmoji: {
|
||||
fontSize: 13,
|
||||
marginLeft: 7
|
||||
marginLeft: 7,
|
||||
color: '#ffffff'
|
||||
},
|
||||
reactionCustomEmoji: {
|
||||
width: 19,
|
||||
|
|
|
@ -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'
|
||||
]
|
||||
};
|
||||
|
||||
|
|