diff --git a/.eslintignore b/.eslintignore index 573933545..398578ec4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,5 @@ __tests__ node_modules coverage e2e +android +ios \ No newline at end of file diff --git a/README.md b/README.md index 0ef048c2f..c672b50d3 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Readme will guide you on how to config. | Channel Info screen -> Members | ✅ | | Channel Info screen -> Pinned | ✅ | | Channel Info screen -> Starred | ✅ | -| Channel Info screen -> Uploads | ❌ | +| Channel Info screen -> Uploads | ✅ | | Star message | ✅ | | Unstar message | ✅ | | Channel Info screen -> Topic | ✅ | @@ -200,7 +200,7 @@ Readme will guide you on how to config. | Deep links: Authentication | ✅ | | Deep links: Rooms | ✅ | | Draft per room | ❌ | -| Localized in Portuguese (pt-BR) | ❌ | +| Localized in Portuguese (pt-BR) | ✅ | | Localized in Russian | ✅ | | Localized in English | ✅ | | Full name setting | ✅ | diff --git a/__tests__/__snapshots__/RoomItem.js.snap b/__tests__/__snapshots__/RoomItem.js.snap index ee37f4b37..049fbc30d 100644 --- a/__tests__/__snapshots__/RoomItem.js.snap +++ b/__tests__/__snapshots__/RoomItem.js.snap @@ -650,6 +650,9 @@ exports[`render unread +999 1`] = ` @@ -912,6 +912,9 @@ exports[`render unread 1`] = ` @@ -1174,6 +1174,9 @@ exports[`renders correctly 1`] = ` diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 36323e9dd..2b24c6a52 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -39,6 +39,9 @@ exports[`Storyshots Avatar avatar 1`] = ` @@ -111,6 +111,9 @@ exports[`Storyshots Avatar avatar 1`] = ` @@ -183,6 +183,9 @@ exports[`Storyshots Avatar avatar 1`] = ` @@ -255,6 +255,9 @@ exports[`Storyshots Avatar avatar 1`] = ` @@ -360,6 +360,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -591,6 +591,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -826,6 +826,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -1083,6 +1083,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -1344,6 +1344,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -1601,6 +1601,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -1858,6 +1858,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -2115,6 +2115,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -2372,6 +2372,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -2603,6 +2603,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` @@ -2834,6 +2834,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` diff --git a/app/Icons.js b/app/Icons.js index b5329d741..c4fab0592 100644 --- a/app/Icons.js +++ b/app/Icons.js @@ -6,13 +6,10 @@ const prefix = isIOS ? 'ios' : 'md'; // icon name from provider: [ size of the uri, icon provider, name to be used later ] const icons = { - [`${ prefix }-search`]: [30, Ionicons, 'search'], - [`${ prefix }-menu`]: [30, Ionicons, 'menu'], [`${ prefix }-star`]: [30, Ionicons, 'star'], [`${ prefix }-star-outline`]: [30, Ionicons, 'starOutline'], - [isIOS ? 'ios-create' : 'md-create']: [30, Ionicons, 'create'], [`${ prefix }-more`]: [30, Ionicons, 'more'], - [`${ prefix }-add`]: [30, Ionicons, 'add'], + [isIOS ? 'ios-create' : 'md-create']: [30, Ionicons, 'create'], [`${ prefix }-close`]: [30, Ionicons, 'close'] }; diff --git a/app/lib/methods/loadMessagesForRoom.js b/app/lib/methods/loadMessagesForRoom.js index 1f5d1307b..11ad8e306 100644 --- a/app/lib/methods/loadMessagesForRoom.js +++ b/app/lib/methods/loadMessagesForRoom.js @@ -14,6 +14,9 @@ async function loadMessagesForRoomRest({ rid: roomId, latest, t }) { const { token, id } = this.ddp._login; const server = this.ddp.url.replace('ws', 'http'); const data = await get({ token, id, server }, `${ types[t] }.history`, { roomId, latest }); + if (!data || data.status === 'error') { + return []; + } return data.messages; } diff --git a/app/lib/methods/loadMissedMessages.js b/app/lib/methods/loadMissedMessages.js index aecba1033..56603fad3 100644 --- a/app/lib/methods/loadMissedMessages.js +++ b/app/lib/methods/loadMissedMessages.js @@ -10,6 +10,9 @@ async function loadMissedMessagesRest({ rid: roomId, lastOpen: lastUpdate }) { const server = this.ddp.url.replace('ws', 'http'); const { result } = await get({ token, id, server }, 'chat.syncMessages', { roomId, lastUpdate }); // TODO: api fix + if (!result) { + return []; + } return result.updated || result.messages; } diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 9101d1b62..1ce6c017e 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -1,5 +1,7 @@ import { put, call, takeLatest } from 'redux-saga/effects'; import { AsyncStorage } from 'react-native'; +import { Navigation } from 'react-native-navigation'; +import { Provider } from 'react-redux'; import { NavigationActions } from '../Navigation'; import { SERVER } from '../actions/actionsTypes'; @@ -10,6 +12,9 @@ import { setRoles } from '../actions/roles'; import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import log from '../utils/log'; +import store from '../lib/createStore'; + +let LoginSignupView = null; const validate = function* validate(server) { return yield RocketChat.testServer(server); @@ -43,6 +48,11 @@ const handleSelectServer = function* handleSelectServer({ server }) { const handleServerRequest = function* handleServerRequest({ server }) { try { + if (LoginSignupView == null) { + LoginSignupView = require('../views/LoginSignupView').default; + Navigation.registerComponent('LoginSignupView', () => LoginSignupView, store, Provider); + } + yield call(validate, server); yield call(NavigationActions.push, { screen: 'LoginSignupView', title: server, backButtonTitle: '' }); database.databases.serversDB.write(() => { diff --git a/app/utils/deviceInfo.js b/app/utils/deviceInfo.js index dc963537a..c57b11da8 100644 --- a/app/utils/deviceInfo.js +++ b/app/utils/deviceInfo.js @@ -1,7 +1,9 @@ import DeviceInfo from 'react-native-device-info'; +const NOTCH_DEVICES = ['iPhone X', 'iPhone XS', 'iPhone XS Max', 'iPhone XR']; + export default { - isNotch: () => DeviceInfo.getModel() === 'iPhone X', + isNotch: () => NOTCH_DEVICES.includes(DeviceInfo.getModel()), getBrand: () => DeviceInfo.getBrand(), getReadableVersion: () => DeviceInfo.getReadableVersion() }; diff --git a/app/views/LoginSignupView.js b/app/views/LoginSignupView.js index f1a141d4d..fa5b02837 100644 --- a/app/views/LoginSignupView.js +++ b/app/views/LoginSignupView.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet, SafeAreaView } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import Icon from 'react-native-vector-icons/FontAwesome'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import { Base64 } from 'js-base64'; @@ -16,6 +17,7 @@ import random from '../utils/random'; import Button from '../containers/Button'; import Loading from '../containers/Loading'; import I18n from '../i18n'; +import store from '../lib/createStore'; const styles = StyleSheet.create({ container: { @@ -45,6 +47,10 @@ const styles = StyleSheet.create({ } }); +let OAuthView = null; +let LoginView = null; +let RegisterView = null; + @connect(state => ({ server: state.server.server, isFetching: state.login.isFetching, @@ -181,6 +187,11 @@ export default class LoginSignupView extends LoggedView { } openOAuth = (oAuthUrl) => { + if (OAuthView == null) { + OAuthView = require('./OAuthView').default; + Navigation.registerComponent('OAuthView', () => OAuthView, store, Provider); + } + const { navigator } = this.props; navigator.showModal({ screen: 'OAuthView', @@ -192,6 +203,11 @@ export default class LoginSignupView extends LoggedView { } login = () => { + if (LoginView == null) { + LoginView = require('./LoginView').default; + Navigation.registerComponent('LoginView', () => LoginView, store, Provider); + } + const { navigator, server } = this.props; navigator.push({ screen: 'LoginView', @@ -201,6 +217,11 @@ export default class LoginSignupView extends LoggedView { } register = () => { + if (RegisterView == null) { + RegisterView = require('./RegisterView').default; + Navigation.registerComponent('RegisterView', () => RegisterView, store, Provider); + } + const { navigator, server } = this.props; navigator.push({ screen: 'RegisterView', diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 2b6d3e564..83b599155 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { Keyboard, Text, ScrollView, View, SafeAreaView } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import { Answers } from 'react-native-fabric'; import RocketChat from '../lib/rocketchat'; @@ -17,6 +18,10 @@ import { showToast } from '../utils/info'; import { COLOR_BUTTON_PRIMARY } from '../constants/colors'; import LoggedView from './View'; import I18n from '../i18n'; +import store from '../lib/createStore'; + +let RegisterView = null; +let ForgotPasswordView = null; @connect(state => ({ server: state.server.server, @@ -69,6 +74,11 @@ export default class LoginView extends LoggedView { } register = () => { + if (RegisterView == null) { + RegisterView = require('./RegisterView').default; + Navigation.registerComponent('RegisterView', () => RegisterView, store, Provider); + } + const { navigator, server } = this.props; navigator.push({ screen: 'RegisterView', @@ -78,6 +88,11 @@ export default class LoginView extends LoggedView { } forgotPassword = () => { + if (ForgotPasswordView == null) { + ForgotPasswordView = require('./ForgotPasswordView').default; + Navigation.registerComponent('ForgotPasswordView', () => ForgotPasswordView, store, Provider); + } + const { navigator } = this.props; navigator.push({ screen: 'ForgotPasswordView', diff --git a/app/views/NewMessageView.js b/app/views/NewMessageView.js index 898926aac..17bf4c7d8 100644 --- a/app/views/NewMessageView.js +++ b/app/views/NewMessageView.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import database from '../lib/realm'; import RocketChat from '../lib/rocketchat'; @@ -14,6 +15,7 @@ import sharedStyles from './Styles'; import I18n from '../i18n'; import Touch from '../utils/touch'; import SearchBox from '../containers/SearchBox'; +import store from '../lib/createStore'; const styles = StyleSheet.create({ safeAreaView: { @@ -43,6 +45,8 @@ const styles = StyleSheet.create({ } }); +let SelectedUsersView = null; + @connect(state => ({ baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' })) @@ -110,6 +114,11 @@ export default class NewMessageView extends LoggedView { } createChannel = () => { + if (SelectedUsersView == null) { + SelectedUsersView = require('./SelectedUsersView').default; + Navigation.registerComponent('SelectedUsersView', () => SelectedUsersView, store, Provider); + } + const { navigator } = this.props; navigator.push({ screen: 'SelectedUsersView', diff --git a/app/views/OnboardingView/index.js b/app/views/OnboardingView/index.js index 1efdfc93d..8883ccfe9 100644 --- a/app/views/OnboardingView/index.js +++ b/app/views/OnboardingView/index.js @@ -4,7 +4,8 @@ import { } from 'react-native'; import PropTypes from 'prop-types'; import Icon from 'react-native-vector-icons/MaterialIcons'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import { selectServerRequest, serverInitAdd, serverFinishAdd } from '../../actions/server'; import I18n from '../../i18n'; @@ -13,6 +14,9 @@ import Button from './Button'; import styles from './styles'; import LoggedView from '../View'; import DeviceInfo from '../../utils/deviceInfo'; +import store from '../../lib/createStore'; + +let NewServerView = null; @connect(state => ({ currentServer: state.server.server, @@ -63,6 +67,11 @@ export default class OnboardingView extends LoggedView { } connectServer = () => { + if (NewServerView == null) { + NewServerView = require('../NewServerView').default; + Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); + } + const { navigator } = this.props; navigator.push({ screen: 'NewServerView', @@ -74,6 +83,11 @@ export default class OnboardingView extends LoggedView { } joinCommunity = () => { + if (NewServerView == null) { + NewServerView = require('../NewServerView').default; + Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); + } + const { navigator } = this.props; navigator.push({ screen: 'NewServerView', diff --git a/app/views/RegisterView.js b/app/views/RegisterView.js index 4926c322d..94756e1e5 100644 --- a/app/views/RegisterView.js +++ b/app/views/RegisterView.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { Keyboard, Text, View, ScrollView, SafeAreaView } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import { registerSubmit as registerSubmitAction, setUsernameSubmit as setUsernameSubmitAction } from '../actions/login'; import TextInput from '../containers/TextInput'; @@ -15,6 +16,10 @@ import { showToast } from '../utils/info'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import LoggedView from './View'; import I18n from '../i18n'; +import store from '../lib/createStore'; + +let TermsServiceView = null; +let PrivacyPolicyView = null; @connect(state => ({ server: state.server.server, @@ -97,6 +102,11 @@ export default class RegisterView extends LoggedView { } termsService = () => { + if (TermsServiceView == null) { + TermsServiceView = require('./TermsServiceView').default; + Navigation.registerComponent('TermsServiceView', () => TermsServiceView, store, Provider); + } + const { navigator } = this.props; navigator.push({ screen: 'TermsServiceView', @@ -106,6 +116,11 @@ export default class RegisterView extends LoggedView { } privacyPolicy = () => { + if (PrivacyPolicyView == null) { + PrivacyPolicyView = require('./PrivacyPolicyView').default; + Navigation.registerComponent('PrivacyPolicyView', () => PrivacyPolicyView, store, Provider); + } + const { navigator } = this.props; navigator.push({ screen: 'PrivacyPolicyView', diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 1c0cf5cc6..b8ba9bd17 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -5,7 +5,8 @@ import { } from 'react-native'; import Icon from 'react-native-vector-icons/Ionicons'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import { leaveRoom as leaveRoomAction } from '../../actions/room'; import LoggedView from '../View'; @@ -20,9 +21,12 @@ import log from '../../utils/log'; import RoomTypeIcon from '../../containers/RoomTypeIcon'; import I18n from '../../i18n'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; +import store from '../../lib/createStore'; const renderSeparator = () => ; +const modules = {}; + @connect(state => ({ userId: state.login.user && state.login.user.id, username: state.login.user && state.login.user.username, @@ -68,6 +72,10 @@ export default class RoomActionsView extends LoggedView { const { navigator } = this.props; if (item.route) { + if (modules[item.route] == null) { + modules[item.route] = item.require(); + Navigation.registerComponent(item.route, () => modules[item.route], store, Provider); + } navigator.push({ screen: item.route, title: item.name, @@ -80,7 +88,7 @@ export default class RoomActionsView extends LoggedView { } } - get canAddUser() { // Invite user + get canAddUser() { const { rid, t } = this.room; @@ -128,7 +136,8 @@ export default class RoomActionsView extends LoggedView { name: I18n.t('Room_Info'), route: 'RoomInfoView', params: { rid }, - testID: 'room-actions-info' + testID: 'room-actions-info', + require: () => require('../RoomInfoView').default }], renderItem: this.renderRoomInfo }, { @@ -154,28 +163,32 @@ export default class RoomActionsView extends LoggedView { name: I18n.t('Files'), route: 'RoomFilesView', params: { rid }, - testID: 'room-actions-files' + testID: 'room-actions-files', + require: () => require('../RoomFilesView').default }, { icon: 'ios-at', name: I18n.t('Mentions'), route: 'MentionedMessagesView', params: { rid }, - testID: 'room-actions-mentioned' + testID: 'room-actions-mentioned', + require: () => require('../MentionedMessagesView').default }, { icon: 'ios-star', name: I18n.t('Starred'), route: 'StarredMessagesView', params: { rid }, - testID: 'room-actions-starred' + testID: 'room-actions-starred', + require: () => require('../StarredMessagesView').default }, { icon: 'ios-search', name: I18n.t('Search'), route: 'SearchMessagesView', params: { rid }, - testID: 'room-actions-search' + testID: 'room-actions-search', + require: () => require('../SearchMessagesView').default }, { icon: 'ios-share', @@ -188,14 +201,16 @@ export default class RoomActionsView extends LoggedView { name: I18n.t('Pinned'), route: 'PinnedMessagesView', params: { rid }, - testID: 'room-actions-pinned' + testID: 'room-actions-pinned', + require: () => require('../PinnedMessagesView').default }, { icon: 'ios-code', name: I18n.t('Snippets'), route: 'SnippetedMessagesView', params: { rid }, - testID: 'room-actions-snippeted' + testID: 'room-actions-snippeted', + require: () => require('../SnippetedMessagesView').default }, { icon: `ios-notifications${ notifications ? '' : '-off' }`, @@ -232,7 +247,8 @@ export default class RoomActionsView extends LoggedView { : I18n.t('N_online_members', { n: onlineMembers.length })), route: 'RoomMembersView', params: { rid, members: onlineMembers }, - testID: 'room-actions-members' + testID: 'room-actions-members', + require: () => require('../RoomMembersView').default }); } @@ -245,7 +261,8 @@ export default class RoomActionsView extends LoggedView { nextAction: 'ADD_USER', rid }, - testID: 'room-actions-add-user' + testID: 'room-actions-add-user', + require: () => require('../SelectedUsersView').default }); } sections[2].data = [...actions, ...sections[2].data]; diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 8876f2a56..b0825ef89 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -3,8 +3,9 @@ import PropTypes from 'prop-types'; import { View, Text, ScrollView, SafeAreaView } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; import moment from 'moment'; +import { Navigation } from 'react-native-navigation'; import LoggedView from '../View'; import Status from '../../containers/status'; @@ -18,6 +19,7 @@ import log from '../../utils/log'; import RoomTypeIcon from '../../containers/RoomTypeIcon'; import I18n from '../../i18n'; import { iconsMap } from '../../Icons'; +import store from '../../lib/createStore'; const PERMISSION_EDIT_ROOM = 'edit-room'; @@ -32,6 +34,8 @@ const getRoomTitle = room => (room.t === 'd' ) ); +let RoomInfoEditView = null; + @connect(state => ({ baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', userId: state.login.user && state.login.user.id, @@ -80,6 +84,11 @@ export default class RoomInfoView extends LoggedView { const { rid, navigator } = this.props; if (event.type === 'NavBarButtonPress') { if (event.id === 'edit') { + if (RoomInfoEditView == null) { + RoomInfoEditView = require('../RoomInfoEditView').default; + Navigation.registerComponent('RoomInfoEditView', () => RoomInfoEditView, store, Provider); + } + navigator.push({ screen: 'RoomInfoEditView', title: I18n.t('Room_Info_Edit'), diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 24d0d5870..2d8a42e51 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import { Text, View, LayoutAnimation, ActivityIndicator, SafeAreaView } from 'react-native'; -import { connect } from 'react-redux'; -import equal from 'deep-equal'; +import { connect, Provider } from 'react-redux'; import { RectButton } from 'react-native-gesture-handler'; +import { Navigation } from 'react-native-navigation'; import { openRoom as openRoomAction, closeRoom as closeRoomAction, setLastOpen as setLastOpenAction } from '../../actions/room'; import { toggleReactionPicker as toggleReactionPickerAction, actionsShow as actionsShowAction } from '../../actions/messages'; @@ -24,6 +24,9 @@ import log from '../../utils/log'; import I18n from '../../i18n'; import debounce from '../../utils/debounce'; import { iconsMap } from '../../Icons'; +import store from '../../lib/createStore'; + +let RoomActionsView = null; @connect(state => ({ user: { @@ -104,8 +107,23 @@ export default class RoomView extends LoggedView { } shouldComponentUpdate(nextProps, nextState) { - const { room } = this.state; - return !(equal(this.props, nextProps) && equal(this.state, nextState) && room.ro === nextState.room.ro); + const { + room, loaded, joined, end + } = this.state; + const { showActions } = this.props; + + if (room.ro !== nextState.room.ro) { + return true; + } else if (loaded !== nextState.loaded) { + return true; + } else if (joined !== nextState.joined) { + return true; + } else if (end !== nextState.end) { + return true; + } else if (showActions !== nextProps.showActions) { + return true; + } + return false; } componentDidUpdate(prevProps, prevState) { @@ -141,6 +159,11 @@ export default class RoomView extends LoggedView { if (event.type === 'NavBarButtonPress') { if (event.id === 'more') { + if (RoomActionsView == null) { + RoomActionsView = require('../RoomActionsView').default; + Navigation.registerComponent('RoomActionsView', () => RoomActionsView, store, Provider); + } + navigator.push({ screen: 'RoomActionsView', title: I18n.t('Actions'), diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 383381d0e..b9f6fd15c 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -3,8 +3,9 @@ import PropTypes from 'prop-types'; import { Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; import { isEqual } from 'lodash'; +import { Navigation } from 'react-native-navigation'; import SearchBox from '../../containers/SearchBox'; import database from '../../lib/realm'; @@ -18,12 +19,15 @@ import SortDropdown from './SortDropdown'; import ServerDropdown from './ServerDropdown'; import Touch from '../../utils/touch'; import { toggleSortDropdown as toggleSortDropdownAction } from '../../actions/rooms'; +import store from '../../lib/createStore'; const ROW_HEIGHT = 70; const SCROLL_OFFSET = 56; const isAndroid = () => Platform.OS === 'android'; const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index }); +const keyExtractor = item => item.rid; + const leftButtons = [{ id: 'settings', icon: { uri: 'settings', scale: Dimensions.get('window').scale }, @@ -42,6 +46,8 @@ if (Platform.OS === 'android') { }); } +let NewMessageView = null; + @connect(state => ({ userId: state.login.user && state.login.user.id, server: state.server.server, @@ -166,6 +172,11 @@ export default class RoomsListView extends LoggedView { const { navigator } = this.props; if (event.type === 'NavBarButtonPress') { if (event.id === 'newMessage') { + if (NewMessageView == null) { + NewMessageView = require('../NewMessageView').default; + Navigation.registerComponent('NewMessageView', () => NewMessageView, store, Provider); + } + navigator.showModal({ screen: 'NewMessageView', title: I18n.t('New_Message'), @@ -383,6 +394,8 @@ export default class RoomsListView extends LoggedView { }, 100); } + getScrollRef = ref => this.scroll = ref + renderHeader = () => { const { search } = this.state; if (search.length > 0) { @@ -456,7 +469,7 @@ export default class RoomsListView extends LoggedView { item.rid} + keyExtractor={keyExtractor} style={styles.list} renderItem={this.renderItem} ItemSeparatorComponent={this.renderSeparator} @@ -485,7 +498,7 @@ export default class RoomsListView extends LoggedView { item.rid} + keyExtractor={keyExtractor} style={styles.list} renderItem={this.renderItem} ItemSeparatorComponent={this.renderSeparator} @@ -519,7 +532,7 @@ export default class RoomsListView extends LoggedView { return ( this.scroll = ref} + ref={this.getScrollRef} contentOffset={Platform.OS === 'ios' ? { x: 0, y: SCROLL_OFFSET } : {}} keyboardShouldPersistTaps='always' testID='rooms-list-view-list' diff --git a/app/views/SelectedUsersView.js b/app/views/SelectedUsersView.js index d8de2fcc3..7fbfc601f 100644 --- a/app/views/SelectedUsersView.js +++ b/app/views/SelectedUsersView.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform } from 'react-native'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import { addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction @@ -18,6 +19,7 @@ import I18n from '../i18n'; import log from '../utils/log'; import SearchBox from '../containers/SearchBox'; import sharedStyles from './Styles'; +import store from '../lib/createStore'; const styles = StyleSheet.create({ safeAreaView: { @@ -32,6 +34,8 @@ const styles = StyleSheet.create({ } }); +let CreateChannelView = null; + @connect(state => ({ baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', users: state.selectedUsers.users, @@ -108,6 +112,11 @@ export default class SelectedUsersView extends LoggedView { if (event.id === 'create') { const { nextAction, setLoadingInvite, navigator } = this.props; if (nextAction === 'CREATE_CHANNEL') { + if (CreateChannelView == null) { + CreateChannelView = require('./CreateChannelView').default; + Navigation.registerComponent('CreateChannelView', () => CreateChannelView, store, Provider); + } + navigator.push({ screen: 'CreateChannelView', title: I18n.t('Create_Channel'), diff --git a/app/views/index.js b/app/views/index.js index 1e26cabea..c48953fdf 100644 --- a/app/views/index.js +++ b/app/views/index.js @@ -2,64 +2,22 @@ import { Navigation } from 'react-native-navigation'; import { Provider } from 'react-redux'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; -import CreateChannelView from './CreateChannelView'; -import ForgotPasswordView from './ForgotPasswordView'; -import LoginSignupView from './LoginSignupView'; -import LoginView from './LoginView'; -import MentionedMessagesView from './MentionedMessagesView'; -import NewMessageView from './NewMessageView'; -import NewServerView from './NewServerView'; -import OAuthView from './OAuthView'; import OnboardingView from './OnboardingView'; -import PinnedMessagesView from './PinnedMessagesView'; -import PrivacyPolicyView from './PrivacyPolicyView'; import ProfileView from './ProfileView'; -import RegisterView from './RegisterView'; -import RoomActionsView from './RoomActionsView'; -import RoomFilesView from './RoomFilesView'; -import RoomInfoEditView from './RoomInfoEditView'; -import RoomInfoView from './RoomInfoView'; -import RoomMembersView from './RoomMembersView'; import RoomsListHeaderView from './RoomsListView/Header'; import RoomsListSearchView from './RoomsListView/Search'; import RoomsListView from './RoomsListView'; import RoomView from './RoomView'; -import SearchMessagesView from './SearchMessagesView'; -import SelectedUsersView from './SelectedUsersView'; import SettingsView from './SettingsView'; import Sidebar from '../containers/Sidebar'; -import SnippetedMessagesView from './SnippetedMessagesView'; -import StarredMessagesView from './StarredMessagesView'; -import TermsServiceView from './TermsServiceView'; export const registerScreens = (store) => { - Navigation.registerComponent('CreateChannelView', () => CreateChannelView, store, Provider); - Navigation.registerComponent('ForgotPasswordView', () => ForgotPasswordView, store, Provider); - Navigation.registerComponent('LoginSignupView', () => LoginSignupView, store, Provider); - Navigation.registerComponent('LoginView', () => LoginView, store, Provider); - Navigation.registerComponent('MentionedMessagesView', () => gestureHandlerRootHOC(MentionedMessagesView), store, Provider); - Navigation.registerComponent('NewMessageView', () => NewMessageView, store, Provider); - Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); - Navigation.registerComponent('OAuthView', () => OAuthView, store, Provider); Navigation.registerComponent('OnboardingView', () => OnboardingView, store, Provider); - Navigation.registerComponent('PinnedMessagesView', () => gestureHandlerRootHOC(PinnedMessagesView), store, Provider); - Navigation.registerComponent('PrivacyPolicyView', () => PrivacyPolicyView, store, Provider); Navigation.registerComponent('ProfileView', () => ProfileView, store, Provider); - Navigation.registerComponent('RegisterView', () => RegisterView, store, Provider); - Navigation.registerComponent('RoomActionsView', () => RoomActionsView, store, Provider); - Navigation.registerComponent('RoomFilesView', () => gestureHandlerRootHOC(RoomFilesView), store, Provider); - Navigation.registerComponent('RoomInfoEditView', () => RoomInfoEditView, store, Provider); - Navigation.registerComponent('RoomInfoView', () => RoomInfoView, store, Provider); - Navigation.registerComponent('RoomMembersView', () => RoomMembersView, store, Provider); Navigation.registerComponent('RoomsListHeaderView', () => RoomsListHeaderView, store, Provider); Navigation.registerComponent('RoomsListSearchView', () => RoomsListSearchView, store, Provider); Navigation.registerComponent('RoomsListView', () => gestureHandlerRootHOC(RoomsListView), store, Provider); Navigation.registerComponent('RoomView', () => gestureHandlerRootHOC(RoomView), store, Provider); - Navigation.registerComponent('SearchMessagesView', () => gestureHandlerRootHOC(SearchMessagesView), store, Provider); - Navigation.registerComponent('SelectedUsersView', () => SelectedUsersView, store, Provider); Navigation.registerComponent('SettingsView', () => SettingsView, store, Provider); Navigation.registerComponent('Sidebar', () => Sidebar, store, Provider); - Navigation.registerComponent('SnippetedMessagesView', () => gestureHandlerRootHOC(SnippetedMessagesView), store, Provider); - Navigation.registerComponent('StarredMessagesView', () => gestureHandlerRootHOC(StarredMessagesView), store, Provider); - Navigation.registerComponent('TermsServiceView', () => TermsServiceView, store, Provider); }; diff --git a/package-lock.json b/package-lock.json index 6e02d1e72..36fcad919 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6788,9 +6788,9 @@ } }, "detox": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/detox/-/detox-9.0.1.tgz", - "integrity": "sha512-KT0Sptt4JvWHTXP1dxMzqXc/LCJxZIB9bKGtjDjg6hdLwOmLAxY3Zfz22ASKGC33rLT6dw/uvyODjgwfVzlYfw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/detox/-/detox-9.0.2.tgz", + "integrity": "sha512-TRPvaQpiWOzkYzh+qybefvPNHj7dSffY2kRQoKC7iJbMZUWfw5JNJ5htYEwvzH8g6kBR4zzlsYbf4/AwJvkQqw==", "dev": true, "requires": { "bunyan": "^1.8.12", @@ -13737,7 +13737,7 @@ }, "rimraf": { "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "dev": true, "optional": true, @@ -13873,7 +13873,7 @@ }, "ncp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "dev": true, "optional": true @@ -16709,9 +16709,9 @@ } }, "react-native-device-info": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-0.22.5.tgz", - "integrity": "sha512-Pe+7eoWOR/TmZxWLaa8rmEmqRckyvPnniDXjiKxaUSk5oUCCtHjFajVYSVkWoJGPwd4xQYjrhx1gr/tcvjSEwQ==" + "version": "0.22.6", + "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-0.22.6.tgz", + "integrity": "sha512-/w87PmHPvT5MY3HwzdVEWyFg/ib91lY6TlYJGNnNPaXNXY60ShB8HI+GXKyq4KAtbUc/WhRM1RQ8/WKJOal+UQ==" }, "react-native-dialog": { "version": "5.1.0", @@ -16728,9 +16728,9 @@ "integrity": "sha512-NKYXiH8w/DjNXoozko1twjAd5F8shL/UiTVx/PQ8QNaasWpbxlXgeJ5exhDSIcDXao0AUdcPWJunYdSCjq208g==" }, "react-native-fast-image": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.0.3.tgz", - "integrity": "sha512-70XlQPt8b7yQSMwUEEIN5jTx7KOx1EBD2XhIRIEHChfNv5Gwn8dh28RSo/Dq9qezf4CWJXO3CAb4lq+Hu9d0vw==" + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.0.10.tgz", + "integrity": "sha512-9WRINd7xk0Hhv3ublbJqGpU+dEApzMmabHU/5ULGvQ0LBWdCFF/4ScQMPJlI0dnCMu/++/emRshn4ML6sQ1yhQ==" }, "react-native-fit-image": { "version": "1.5.4", @@ -19775,9 +19775,9 @@ } }, "tail": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tail/-/tail-1.3.0.tgz", - "integrity": "sha512-9Blh9bCW3lQyr10UAh//7K3kqljspQ+NcMa5nwVXicnxFXfiUizZrEC71kqVKPhe2UcMLXDEb+YnqR+tzvOEDQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tail/-/tail-1.4.0.tgz", + "integrity": "sha512-wjwfZw6wcMFTB1Po7NFUf4TdCDwX8duZjdTMhnHBEC677Q6mFRcVZE7f/nZDhG2Fpf/wEEKOJP9L7/b11/vlHQ==", "dev": true }, "tapable": { diff --git a/package.json b/package.json index 4ed31eeb7..868c1c11d 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "storybook": "storybook start -p 7007", "snyk-protect": "snyk protect", "fabric-ios": "./scripts/fabric-ios.sh", - "fabric-android": "./scripts/fabric-android.sh", - "postinstall": "cp ./temp/react.gradle ./node_modules/react-native" + "fabric-android": "./scripts/fabric-android.sh" }, "rnpm": { "assets": [ @@ -38,10 +37,10 @@ "react-native": "^0.57.1", "react-native-actionsheet": "^2.4.2", "react-native-audio": "^4.2.0", - "react-native-device-info": "^0.22.5", + "react-native-device-info": "^0.22.6", "react-native-dialog": "^5.1.0", "react-native-fabric": "^0.5.1", - "react-native-fast-image": "^5.0.3", + "react-native-fast-image": "^5.0.10", "react-native-gesture-handler": "^1.0.7", "react-native-i18n": "^2.0.15", "react-native-image-crop-picker": "git+https://github.com/RocketChat/react-native-image-crop-picker.git", @@ -87,7 +86,7 @@ "babel-plugin-transform-remove-console": "^6.9.4", "babel-runtime": "^6.26.0", "codecov": "^3.1.0", - "detox": "^9.0.1", + "detox": "^9.0.2", "eslint": "^5.6.0", "eslint-config-airbnb": "^17.1.0", "eslint-plugin-import": "^2.14.0", diff --git a/temp/react.gradle b/temp/react.gradle deleted file mode 100644 index 560007b54..000000000 --- a/temp/react.gradle +++ /dev/null @@ -1,135 +0,0 @@ -// Workaround to "error: Duplicate file. Original is here. The version qualifier may be implied. With gradle assembleRelease" -// https://github.com/facebook/react-native/issues/19211#issuecomment-389125030 -import org.apache.tools.ant.taskdefs.condition.Os - -def config = project.hasProperty("react") ? project.react : []; - -def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js" -def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" -def entryFile = config.entryFile ?: "index.js" -def bundleCommand = config.bundleCommand ?: "bundle" - -// because elvis operator -def elvisFile(thing) { - return thing ? file(thing) : null; -} - -def reactRoot = elvisFile(config.root) ?: file("../../") -def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] -def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ; - -void runBefore(String dependentTaskName, Task task) { - Task dependentTask = tasks.findByPath(dependentTaskName); - if (dependentTask != null) { - dependentTask.dependsOn task - } -} - -gradle.projectsEvaluated { - // Grab all build types and product flavors - def buildTypes = android.buildTypes.collect { type -> type.name } - def productFlavors = android.productFlavors.collect { flavor -> flavor.name } - - // When no product flavors defined, use empty - if (!productFlavors) productFlavors.add('') - - productFlavors.each { productFlavorName -> - buildTypes.each { buildTypeName -> - // Create variant and target names - def flavorNameCapitalized = "${productFlavorName.capitalize()}" - def buildNameCapitalized = "${buildTypeName.capitalize()}" - def targetName = "${flavorNameCapitalized}${buildNameCapitalized}" - def targetPath = productFlavorName ? - "${productFlavorName}/${buildTypeName}" : - "${buildTypeName}" - - // React js bundle directories - def jsBundleDirConfigName = "jsBundleDir${targetName}" - def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?: - file("$buildDir/intermediates/assets/${targetPath}") - - def resourcesDirConfigName = "resourcesDir${targetName}" - def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?: - file("$buildDir/intermediates/res/merged/${targetPath}") - def jsBundleFile = file("$jsBundleDir/$bundleAssetName") - - // Bundle task name for variant - def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets" - - // Additional node and packager commandline arguments - def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"] - def extraPackagerArgs = config.extraPackagerArgs ?: [] - - def currentBundleTask = tasks.create( - name: bundleJsAndAssetsTaskName, - type: Exec) { - group = "react" - description = "bundle JS and assets for ${targetName}." - - // Create dirs if they are not there (e.g. the "clean" task just ran) - doFirst { - jsBundleDir.mkdirs() - resourcesDir.mkdirs() - } - - // Set up inputs and outputs so gradle can cache the result - inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) - outputs.dir jsBundleDir - outputs.dir resourcesDir - - // Set up the call to the react-native cli - workingDir reactRoot - - // Set up dev mode - def devEnabled = !(config."devDisabledIn${targetName}" - || targetName.toLowerCase().contains("release")) - - def extraArgs = extraPackagerArgs; - - if (bundleConfig) { - extraArgs = extraArgs.clone() - extraArgs.add("--config"); - extraArgs.add(bundleConfig); - } - - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", - "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs) - } else { - commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", - "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs) - } - - enabled config."bundleIn${targetName}" || - config."bundleIn${buildTypeName.capitalize()}" ?: - targetName.toLowerCase().contains("release") - - doLast { - def moveFunc = { resSuffix -> - File originalDir = file("${resourcesDir}/drawable-${resSuffix}") - if (originalDir.exists()) { - File destDir = file("${resourcesDir}/drawable-${resSuffix}-v4") - ant.move(file: originalDir, tofile: destDir) - } - } - moveFunc.curry("ldpi").call() - moveFunc.curry("mdpi").call() - moveFunc.curry("hdpi").call() - moveFunc.curry("xhdpi").call() - moveFunc.curry("xxhdpi").call() - moveFunc.curry("xxxhdpi").call() - } - } - - // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process - currentBundleTask.dependsOn("merge${targetName}Resources") - currentBundleTask.dependsOn("merge${targetName}Assets") - - runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask) - runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask) - runBefore("processUniversal${targetName}Resources", currentBundleTask) - runBefore("process${targetName}Resources", currentBundleTask) - runBefore("dataBindingProcessLayouts${targetName}", currentBundleTask) - } - } -}