[NEW] Onboarding (#407)

<!-- INSTRUCTION: Keep the line below to notify all core developers about this new PR -->
@RocketChat/ReactNative

<!-- INSTRUCTION: Inform the issue number that this PR closes, or remove the line below -->
Closes #392 

<!-- INSTRUCTION: Tell us more about your PR with screen shots if you can -->
![aug-07-2018 17-03-50](https://user-images.githubusercontent.com/804994/43799447-f62074dc-9a63-11e8-8aac-bf2c4c5a8a2b.gif)
![aug-07-2018 17-03-35](https://user-images.githubusercontent.com/804994/43799446-f5f84a70-9a63-11e8-8947-265113ae9bf4.gif)
![aug-07-2018 17-03-13](https://user-images.githubusercontent.com/804994/43799445-f5d70ee6-9a63-11e8-94a9-f49c7d69fbba.gif)
This commit is contained in:
Diego Mello 2018-08-10 14:26:36 -03:00 committed by Guilherme Gazzo
parent 701baed9fa
commit 91025e9d03
71 changed files with 1270 additions and 1228 deletions

View File

@ -71,12 +71,12 @@ jobs:
- run: - run:
name: Build name: Build
command: | command: |
detox build detox build --configuration ios.sim.release
- run: - run:
name: Test name: Test
command: | command: |
detox test detox test --configuration ios.sim.release --cleanup
- store_artifacts: - store_artifacts:
path: /tmp/screenshots path: /tmp/screenshots

View File

@ -102,6 +102,7 @@ exports[`render channel 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -259,6 +260,7 @@ exports[`render no icon 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -416,6 +418,7 @@ exports[`render private group 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -636,6 +639,7 @@ exports[`render unread +999 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -875,6 +879,7 @@ exports[`render unread 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -1114,6 +1119,7 @@ exports[`renders correctly 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >

View File

@ -460,6 +460,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -674,6 +675,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -892,6 +894,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -1126,6 +1129,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -1364,6 +1368,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -1598,6 +1603,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -1832,6 +1838,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -2066,6 +2073,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -2300,6 +2308,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -2514,6 +2523,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >
@ -2728,6 +2738,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
"fontStyle": "normal", "fontStyle": "normal",
"fontWeight": "normal", "fontWeight": "normal",
}, },
Object {},
] ]
} }
> >

View File

@ -10,9 +10,10 @@ const icons = {
[`${ prefix }-menu`]: [30, Ionicons, 'menu'], [`${ prefix }-menu`]: [30, Ionicons, 'menu'],
[`${ prefix }-star`]: [30, Ionicons, 'star'], [`${ prefix }-star`]: [30, Ionicons, 'star'],
[`${ prefix }-star-outline`]: [30, Ionicons, 'starOutline'], [`${ prefix }-star-outline`]: [30, Ionicons, 'starOutline'],
[isIOS ? 'ios-create-outline' : 'md-create']: [30, Ionicons, 'create'], [isIOS ? 'ios-create' : 'md-create']: [30, Ionicons, 'create'],
[`${ prefix }-more`]: [30, Ionicons, 'more'], [`${ prefix }-more`]: [30, Ionicons, 'more'],
[`${ prefix }-add`]: [30, Ionicons, 'add'] [`${ prefix }-add`]: [30, Ionicons, 'add'],
[`${ prefix }-close`]: [30, Ionicons, 'close']
}; };
const iconsMap = {}; const iconsMap = {};

View File

@ -8,6 +8,7 @@ class NavigationActionsClass {
popToRoot = params => this.navigator && this.navigator.popToRoot(params); popToRoot = params => this.navigator && this.navigator.popToRoot(params);
resetTo = params => this.navigator && this.navigator.resetTo(params); resetTo = params => this.navigator && this.navigator.resetTo(params);
toggleDrawer = params => this.navigator && this.navigator.toggleDrawer(params); toggleDrawer = params => this.navigator && this.navigator.toggleDrawer(params);
dismissModal = params => this.navigator && this.navigator.dismissModal(params);
} }
export const NavigationActions = new NavigationActionsClass(); export const NavigationActions = new NavigationActionsClass();

View File

@ -76,9 +76,7 @@ export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
export const SERVER = createRequestTypes('SERVER', [ export const SERVER = createRequestTypes('SERVER', [
...defaultTypes, ...defaultTypes,
'SELECT_SUCCESS', 'SELECT_SUCCESS',
'SELECT_REQUEST', 'SELECT_REQUEST'
'CHANGED',
'ADD'
]); ]);
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']); export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']);
export const LOGOUT = 'LOGOUT'; // logout is always success export const LOGOUT = 'LOGOUT'; // logout is always success

View File

@ -21,14 +21,6 @@ export function serverRequest(server) {
}; };
} }
export function addServer(server) {
return {
type: SERVER.ADD,
server
};
}
export function serverSuccess() { export function serverSuccess() {
return { return {
type: SERVER.SUCCESS type: SERVER.SUCCESS
@ -41,11 +33,3 @@ export function serverFailure(err) {
err err
}; };
} }
export function changedServer(server) {
return {
type: SERVER.CHANGED,
server
};
}

View File

@ -1,48 +1,53 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StyleSheet, View, Text, Platform } from 'react-native'; import { StyleSheet, View, Text, Platform, ActivityIndicator } from 'react-native';
import { COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../../constants/colors'; import { COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../../constants/colors';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { scale, moderateScale, verticalScale } from '../../utils/scaling';
const colors = { const colors = {
backgroundPrimary: COLOR_BUTTON_PRIMARY, background_primary: COLOR_BUTTON_PRIMARY,
backgroundSecondary: 'white', background_secondary: 'white',
textColorPrimary: 'white', text_color_primary: 'white',
textColorSecondary: COLOR_TEXT text_color_secondary: COLOR_TEXT
}; };
/* eslint-disable react-native/no-unused-styles */ /* eslint-disable react-native/no-unused-styles */
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingHorizontal: 15, paddingHorizontal: scale(15),
paddingVertical: 10 justifyContent: 'center',
height: scale(48)
}, },
text: { text: {
fontSize: moderateScale(18),
height: verticalScale(20),
lineHeight: verticalScale(20),
textAlign: 'center', textAlign: 'center',
fontWeight: '700' fontWeight: '500'
}, },
background_primary: { background_primary: {
backgroundColor: colors.backgroundPrimary backgroundColor: colors.background_primary
}, },
background_secondary: { background_secondary: {
backgroundColor: colors.backgroundSecondary backgroundColor: colors.background_secondary
}, },
text_color_primary: { text_color_primary: {
color: colors.textColorPrimary color: colors.text_color_primary
}, },
text_color_secondary: { text_color_secondary: {
color: colors.textColorSecondary color: colors.text_color_secondary
}, },
margin: { margin: {
marginBottom: 10 marginBottom: verticalScale(10)
}, },
disabled: { disabled: {
opacity: 0.5 opacity: 0.5
}, },
border: { border: {
borderRadius: 2 borderRadius: scale(2)
} }
}); });
@ -53,26 +58,28 @@ export default class Button extends React.PureComponent {
onPress: PropTypes.func, onPress: PropTypes.func,
disabled: PropTypes.bool, disabled: PropTypes.bool,
margin: PropTypes.any, margin: PropTypes.any,
backgroundColor: PropTypes.string backgroundColor: PropTypes.string,
loading: PropTypes.bool
} }
static defaultProps = { static defaultProps = {
title: 'Press me!', title: 'Press me!',
type: 'primary', type: 'primary',
onPress: () => alert('It works!'), onPress: () => alert('It works!'),
disabled: false disabled: false,
loading: false
} }
render() { render() {
const { const {
title, type, onPress, disabled, margin, backgroundColor, ...otherProps title, type, onPress, disabled, margin, backgroundColor, loading, ...otherProps
} = this.props; } = this.props;
return ( return (
<Touch <Touch
onPress={onPress} onPress={onPress}
accessibilityTraits='button' accessibilityTraits='button'
style={Platform.OS === 'ios' && [(margin || styles.margin), styles.border]} style={Platform.OS === 'ios' && [(margin || styles.margin), styles.border]}
disabled={disabled} disabled={disabled || loading}
{...otherProps} {...otherProps}
> >
<View <View
@ -84,7 +91,11 @@ export default class Button extends React.PureComponent {
disabled && styles.disabled disabled && styles.disabled
]} ]}
> >
{
loading ?
<ActivityIndicator color={colors[`text_color_${ type }`]} /> :
<Text style={[styles.text, styles[`text_color_${ type }`]]}>{title}</Text> <Text style={[styles.text, styles[`text_color_${ type }`]]}>{title}</Text>
}
</View> </View>
</Touch> </Touch>
); );

View File

@ -1,12 +1,13 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView } from 'react-native'; import { ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView, AsyncStorage } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import database from '../lib/realm'; import database from '../lib/realm';
import { selectServerRequest } from '../actions/server'; import { selectServerRequest } from '../actions/server';
import { appStart } from '../actions';
import { logout } from '../actions/login'; import { logout } from '../actions/login';
import Avatar from '../containers/Avatar'; import Avatar from '../containers/Avatar';
import Status from '../containers/status'; import Status from '../containers/status';
@ -16,6 +17,7 @@ import RocketChat from '../lib/rocketchat';
import log from '../utils/log'; import log from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
import { NavigationActions } from '../Navigation'; import { NavigationActions } from '../Navigation';
import scrollPersistTaps from '../utils/scrollPersistTaps';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -86,7 +88,8 @@ const keyExtractor = item => item.id;
} }
}), dispatch => ({ }), dispatch => ({
selectServerRequest: server => dispatch(selectServerRequest(server)), selectServerRequest: server => dispatch(selectServerRequest(server)),
logout: () => dispatch(logout()) logout: () => dispatch(logout()),
appStart: () => dispatch(appStart('outside'))
})) }))
export default class Sidebar extends Component { export default class Sidebar extends Component {
static propTypes = { static propTypes = {
@ -94,7 +97,8 @@ export default class Sidebar extends Component {
server: PropTypes.string.isRequired, server: PropTypes.string.isRequired,
selectServerRequest: PropTypes.func.isRequired, selectServerRequest: PropTypes.func.isRequired,
user: PropTypes.object, user: PropTypes.object,
logout: PropTypes.func.isRequired logout: PropTypes.func.isRequired,
appStart: PropTypes.func
} }
constructor(props) { constructor(props) {
@ -206,7 +210,7 @@ export default class Sidebar extends Component {
try { try {
RocketChat.setUserPresenceDefaultStatus(item.id); RocketChat.setUserPresenceDefaultStatus(item.id);
} catch (e) { } catch (e) {
log('onPressModalButton', e); log('setUserPresenceDefaultStatus', e);
} }
} }
} }
@ -226,6 +230,21 @@ export default class Sidebar extends Component {
this.toggleServers(); this.toggleServers();
if (this.props.server !== item.id) { if (this.props.server !== item.id) {
this.props.selectServerRequest(item.id); this.props.selectServerRequest(item.id);
const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ item.id }`);
if (!token) {
this.props.appStart();
setTimeout(() => {
NavigationActions.push({
screen: 'NewServerView',
passProps: {
server: item.id
},
navigatorStyle: {
navBarHidden: true
}
});
}, 1000);
}
} }
}, },
testID: `sidebar-${ item.id }` testID: `sidebar-${ item.id }`
@ -289,9 +308,12 @@ export default class Sidebar extends Component {
onPress: () => { onPress: () => {
this.closeDrawer(); this.closeDrawer();
this.toggleServers(); this.toggleServers();
NavigationActions.push({ this.props.navigator.showModal({
screen: 'NewServerView', screen: 'NewServerView',
title: I18n.t('Add_Server') title: I18n.t('Add_Server'),
passProps: {
previousServer: this.props.server
}
}); });
}, },
testID: 'sidebar-add-server' testID: 'sidebar-add-server'
@ -305,8 +327,8 @@ export default class Sidebar extends Component {
return null; return null;
} }
return ( return (
<ScrollView style={styles.container}>
<SafeAreaView testID='sidebar' style={styles.container}> <SafeAreaView testID='sidebar' style={styles.container}>
<ScrollView style={styles.container} {...scrollPersistTaps}>
<Touch <Touch
onPress={() => this.toggleServers()} onPress={() => this.toggleServers()}
underlayColor='rgba(255, 255, 255, 0.5)' underlayColor='rgba(255, 255, 255, 0.5)'
@ -338,8 +360,8 @@ export default class Sidebar extends Component {
{!this.state.showServers ? this.renderNavigation() : null} {!this.state.showServers ? this.renderNavigation() : null}
{this.state.showServers ? this.renderServers() : null} {this.state.showServers ? this.renderServers() : null}
</SafeAreaView>
</ScrollView> </ScrollView>
</SafeAreaView>
); );
} }
} }

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { View, StyleSheet, Text, TextInput, ViewPropTypes, Platform } from 'react-native'; import { View, StyleSheet, Text, TextInput, ViewPropTypes, Platform } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
@ -21,13 +20,11 @@ const styles = StyleSheet.create({
fontSize: 14, fontSize: 14,
paddingTop: 12, paddingTop: 12,
paddingBottom: 12, paddingBottom: 12,
// paddingTop: 5,
// paddingBottom: 5,
paddingHorizontal: 10, paddingHorizontal: 10,
borderWidth: 2, borderWidth: 1.5,
borderRadius: 4, borderRadius: 2,
backgroundColor: 'white', backgroundColor: 'white',
borderColor: 'rgba(0,0,0,.15)', borderColor: '#E7EBF2',
color: 'black' color: 'black'
}, },
labelError: { labelError: {

View File

@ -65,7 +65,8 @@ export default class Markdown extends React.Component {
} }
return null; return null;
}, },
blocklink: () => {}, hardbreak: () => null,
blocklink: () => null,
image: node => ( image: node => (
// TODO: should use Image component // TODO: should use Image component
<Image key={node.key} style={styles.inlineImage} source={{ uri: node.attributes.src }} /> <Image key={node.key} style={styles.inlineImage} source={{ uri: node.attributes.src }} />

View File

@ -116,6 +116,7 @@ export default {
Code: 'Code', Code: 'Code',
Colaborative: 'Colaborative', Colaborative: 'Colaborative',
Connect: 'Connect', Connect: 'Connect',
Connect_to_a_server: 'Connect to a server',
Connected_to: 'Connected to', Connected_to: 'Connected to',
Connecting: 'Connecting', Connecting: 'Connecting',
Copied_to_clipboard: 'Copied to clipboard!', Copied_to_clipboard: 'Copied to clipboard!',
@ -123,6 +124,7 @@ export default {
Copy_Permalink: 'Copy Permalink', Copy_Permalink: 'Copy Permalink',
Create_account: 'Create account', Create_account: 'Create account',
Create_Channel: 'Create Channel', Create_Channel: 'Create Channel',
Create_a_new_workspace: 'Create a new workspace',
Create: 'Create', Create: 'Create',
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.', Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
delete: 'delete', delete: 'delete',
@ -153,6 +155,7 @@ export default {
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance', is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance', is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance',
is_typing: 'is typing', is_typing: 'is typing',
Join_the_community: 'Join the community',
Just_invited_people_can_access_this_channel: 'Just invited people can access this channel', Just_invited_people_can_access_this_channel: 'Just invited people can access this channel',
Language: 'Language', Language: 'Language',
last_message: 'last message', last_message: 'last message',
@ -197,9 +200,11 @@ export default {
Notify_active_in_this_room: 'Notify active users in this room', Notify_active_in_this_room: 'Notify active users in this room',
Notify_all_in_this_room: 'Notify all in this room', Notify_all_in_this_room: 'Notify all in this room',
Offline: 'Offline', Offline: 'Offline',
Oops: 'Oops!',
Online: 'Online', Online: 'Online',
Only_authorized_users_can_write_new_messages: 'Only authorized users can write new messages', Only_authorized_users_can_write_new_messages: 'Only authorized users can write new messages',
Open_emoji_selector: 'Open emoji selector', Open_emoji_selector: 'Open emoji selector',
Open_Source_Communication: 'Open Source Communication',
Or_continue_using_social_accounts: 'Or continue using social accounts', Or_continue_using_social_accounts: 'Or continue using social accounts',
Password: 'Password', Password: 'Password',
Permalink_copied_to_clipboard: 'Permalink copied to clipboard!', Permalink_copied_to_clipboard: 'Permalink copied to clipboard!',
@ -274,6 +279,7 @@ export default {
tap_to_change_status: 'tap to change status', tap_to_change_status: 'tap to change status',
Tap_to_view_servers_list: 'Tap to view servers list', Tap_to_view_servers_list: 'Tap to view servers list',
Terms_of_Service: ' Terms of Service ', Terms_of_Service: ' Terms of Service ',
The_URL_is_invalid: 'The URL you entered is invalid. Check it and try again, please!',
There_was_an_error_while_action: 'There was an error while {{action}}!', There_was_an_error_while_action: 'There was an error while {{action}}!',
This_room_is_blocked: 'This room is blocked', This_room_is_blocked: 'This room is blocked',
This_room_is_read_only: 'This room is read only', This_room_is_read_only: 'This room is read only',
@ -307,6 +313,7 @@ export default {
Welcome: 'Welcome', Welcome: 'Welcome',
Welcome_title_pt_1: 'Prepare to take off with', Welcome_title_pt_1: 'Prepare to take off with',
Welcome_title_pt_2: 'the ultimate chat platform', Welcome_title_pt_2: 'the ultimate chat platform',
Welcome_to_RocketChat: 'Welcome to Rocket.Chat',
Yes_action_it: 'Yes, {{action}} it!', Yes_action_it: 'Yes, {{action}} it!',
Yesterday: 'Yesterday', Yesterday: 'Yesterday',
You_are_in_preview_mode: 'You are in preview mode', You_are_in_preview_mode: 'You are in preview mode',

View File

@ -4,7 +4,6 @@ import { Navigation } from 'react-native-navigation';
import store from './lib/createStore'; import store from './lib/createStore';
import { appInit } from './actions'; import { appInit } from './actions';
import database from './lib/realm';
import { iconsLoaded } from './Icons'; import { iconsLoaded } from './Icons';
import { registerScreens } from './views'; import { registerScreens } from './views';
import { deepLinkingOpen } from './actions/deepLinking'; import { deepLinkingOpen } from './actions/deepLinking';
@ -27,21 +26,21 @@ const startLogged = () => {
}); });
}; };
const startNotLogged = (route) => { const startNotLogged = () => {
Navigation.startSingleScreenApp({ Navigation.startSingleScreenApp({
screen: { screen: {
screen: route, screen: 'OnboardingView',
title: route === 'NewServerView' ? I18n.t('New_Server') : I18n.t('Servers') navigatorStyle: {
navBarHidden: true
}
}, },
animationType: 'fade' animationType: 'fade',
appStyle: {
orientation: 'portrait'
}
}); });
}; };
const hasServers = () => {
const db = database.databases.serversDB.objects('servers');
return db.length > 0;
};
const handleOpenURL = ({ url }) => { const handleOpenURL = ({ url }) => {
if (url) { if (url) {
url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, ''); url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, '');
@ -77,11 +76,7 @@ export default class App extends Component {
if (this.currentRoot !== root) { if (this.currentRoot !== root) {
this.currentRoot = root; this.currentRoot = root;
if (root === 'outside') { if (root === 'outside') {
if (hasServers()) { startNotLogged();
startNotLogged('ListServerView');
} else {
startNotLogged('NewServerView');
}
} else if (root === 'inside') { } else if (root === 'inside') {
startLogged(); startLogged();
} }

View File

@ -2,6 +2,9 @@ import normalizeMessage from './normalizeMessage';
// TODO: delete and update // TODO: delete and update
export const merge = (subscription, room) => { export const merge = (subscription, room) => {
if (!subscription) {
return;
}
if (room) { if (room) {
if (room.rid) { if (room.rid) {
subscription.rid = room.rid; subscription.rid = room.rid;

View File

@ -3,11 +3,10 @@ import { SERVER } from '../actions/actionsTypes';
const initialState = { const initialState = {
connecting: false, connecting: false,
connected: false, connected: false,
errorMessage: '',
failure: false, failure: false,
server: '', server: '',
adding: false, loading: true,
loading: true adding: true
}; };
@ -17,14 +16,8 @@ export default function server(state = initialState, action) {
return { return {
...state, ...state,
connecting: true, connecting: true,
failure: false failure: false,
}; adding: true
case SERVER.SUCCESS:
return {
...state,
connecting: false,
connected: true,
failure: false
}; };
case SERVER.FAILURE: case SERVER.FAILURE:
return { return {
@ -32,24 +25,22 @@ export default function server(state = initialState, action) {
connecting: false, connecting: false,
connected: false, connected: false,
failure: true, failure: true,
errorMessage: action.err adding: false
};
case SERVER.ADD:
return {
...state,
adding: true
}; };
case SERVER.SELECT_REQUEST: case SERVER.SELECT_REQUEST:
return { return {
...state, ...state,
server: action.server, server: action.server,
connecting: true,
connected: false,
loading: true loading: true
}; };
case SERVER.SELECT_SUCCESS: case SERVER.SELECT_SUCCESS:
return { return {
...state, ...state,
server: action.server, server: action.server,
adding: false, connecting: false,
connected: true,
loading: false loading: false
}; };
default: default:

View File

@ -12,7 +12,7 @@ const create = function* create(data) {
const handleRequest = function* handleRequest({ data }) { const handleRequest = function* handleRequest({ data }) {
try { try {
yield delay(1000); // yield delay(1000);
const auth = yield select(state => state.login.isAuthenticated); const auth = yield select(state => state.login.isAuthenticated);
if (!auth) { if (!auth) {
yield take(LOGIN.SUCCESS); yield take(LOGIN.SUCCESS);

View File

@ -3,7 +3,7 @@ import { takeLatest, take, select, put } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { appStart } from '../actions'; import { appStart } from '../actions';
import { selectServerRequest, addServer } from '../actions/server'; import { selectServerRequest } from '../actions/server';
import database from '../lib/realm'; import database from '../lib/realm';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { NavigationActions } from '../Navigation'; import { NavigationActions } from '../Navigation';
@ -73,7 +73,7 @@ const handleOpen = function* handleOpen({ params }) {
yield navigate({ params, sameServer: false }); yield navigate({ params, sameServer: false });
} }
} else { } else {
yield put(addServer(host)); yield put(selectServerRequest(host));
} }
} }
}; };

View File

@ -23,6 +23,7 @@ import {
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import log from '../utils/log'; import log from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
import { NavigationActions } from '../Navigation';
const getUser = state => state.login.user; const getUser = state => state.login.user;
const getServer = state => state.server.server; const getServer = state => state.server.server;
@ -44,6 +45,7 @@ const handleLoginSuccess = function* handleLoginSuccess() {
yield put(registerIncomplete()); yield put(registerIncomplete());
} else { } else {
yield delay(300); yield delay(300);
NavigationActions.dismissModal();
yield put(appStart('inside')); yield put(appStart('inside'));
} }
} catch (e) { } catch (e) {
@ -103,7 +105,10 @@ const handleLogout = function* handleLogout() {
}; };
const handleRegisterIncomplete = function* handleRegisterIncomplete() { const handleRegisterIncomplete = function* handleRegisterIncomplete() {
const server = yield select(state => state.server);
if (!server.adding) {
yield put(appStart('outside')); yield put(appStart('outside'));
}
}; };
const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ email }) { const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ email }) {

View File

@ -1,17 +1,15 @@
import { put, call, takeLatest } from 'redux-saga/effects'; import { put, call, takeLatest } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import { AsyncStorage } from 'react-native'; import { AsyncStorage } from 'react-native';
import { NavigationActions } from '../Navigation'; import { NavigationActions } from '../Navigation';
import { SERVER } from '../actions/actionsTypes'; import { SERVER } from '../actions/actionsTypes';
import * as actions from '../actions'; import * as actions from '../actions';
import { connectRequest } from '../actions/connect'; import { connectRequest } from '../actions/connect';
import { serverSuccess, serverFailure, selectServerRequest, selectServerSuccess } from '../actions/server'; import { serverFailure, selectServerRequest, selectServerSuccess } from '../actions/server';
import { setRoles } from '../actions/roles'; import { setRoles } from '../actions/roles';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import database from '../lib/realm'; import database from '../lib/realm';
import log from '../utils/log'; import log from '../utils/log';
import I18n from '../i18n';
const validate = function* validate(server) { const validate = function* validate(server) {
return yield RocketChat.testServer(server); return yield RocketChat.testServer(server);
@ -24,8 +22,6 @@ const handleSelectServer = function* handleSelectServer({ server }) {
const token = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`); const token = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
if (token) { if (token) {
yield put(actions.appStart('inside')); yield put(actions.appStart('inside'));
} else {
yield put(actions.appStart('outside'));
} }
const settings = database.objects('settings'); const settings = database.objects('settings');
@ -45,33 +41,22 @@ const handleSelectServer = function* handleSelectServer({ server }) {
} }
}; };
const validateServer = function* validateServer({ server }) { const handleServerRequest = function* handleServerRequest({ server }) {
try { try {
yield delay(1000);
yield call(validate, server); yield call(validate, server);
yield put(serverSuccess()); yield call(NavigationActions.push, { screen: 'LoginSignupView', title: server });
} catch (e) {
console.warn('validateServer', e);
yield put(serverFailure(e));
}
};
const addServer = function* addServer({ server }) {
try {
yield put(actions.appStart('outside'));
yield call(NavigationActions.resetTo, { screen: 'ListServerView', title: I18n.t('Servers') });
database.databases.serversDB.write(() => { database.databases.serversDB.write(() => {
database.databases.serversDB.create('servers', { id: server, current: false }, true); database.databases.serversDB.create('servers', { id: server, current: false }, true);
}); });
yield put(selectServerRequest(server)); yield put(selectServerRequest(server));
} catch (e) { } catch (e) {
log('addServer', e); yield put(serverFailure());
log('handleServerRequest', e);
} }
}; };
const root = function* root() { const root = function* root() {
yield takeLatest(SERVER.REQUEST, validateServer);
yield takeLatest(SERVER.SELECT_REQUEST, handleSelectServer); yield takeLatest(SERVER.SELECT_REQUEST, handleSelectServer);
yield takeLatest(SERVER.ADD, addServer); yield takeLatest(SERVER.REQUEST, handleServerRequest);
}; };
export default root; export default root;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -1,4 +1,4 @@
import CustomTabsAndroid from '../nativeModules/CustomTabsAndroid'; import CustomTabsAndroid from '../../nativeModules/CustomTabsAndroid';
const openLink = url => CustomTabsAndroid.openURL(url); const openLink = url => CustomTabsAndroid.openURL(url);

12
app/utils/scaling.js Normal file
View File

@ -0,0 +1,12 @@
import { Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
const guidelineBaseWidth = 375;
const guidelineBaseHeight = 667;
const scale = size => (width / guidelineBaseWidth) * size;
const verticalScale = size => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5) => size + ((scale(size) - size) * factor);
export { scale, verticalScale, moderateScale };

View File

@ -1,205 +0,0 @@
import React from 'react';
import Icon from 'react-native-vector-icons/Ionicons';
import PropTypes from 'prop-types';
import { View, Text, SectionList, StyleSheet, SafeAreaView } from 'react-native';
import { connect } from 'react-redux';
import LoggedView from './View';
import { selectServerRequest } from '../actions/server';
import database from '../lib/realm';
import Fade from '../animations/fade';
import Touch from '../utils/touch';
import I18n from '../i18n';
import { iconsMap } from '../Icons';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
},
separator: {
height: 1,
backgroundColor: '#eee'
},
headerStyle: {
backgroundColor: '#eee',
lineHeight: 24,
paddingLeft: 14,
color: '#888'
},
serverItem: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
padding: 14
},
listItem: {
color: '#666',
flexGrow: 1,
lineHeight: 30
},
serverChecked: {
flexGrow: 0
}
});
@connect(state => ({
server: state.server.server,
login: state.login,
connected: state.meteor.connected
}), dispatch => ({
selectServerRequest: server => dispatch(selectServerRequest(server))
}))
/** @extends React.Component */
export default class ListServerView extends LoggedView {
static propTypes = {
navigator: PropTypes.object,
login: PropTypes.object.isRequired,
selectServerRequest: PropTypes.func.isRequired,
server: PropTypes.string
}
constructor(props) {
super('ListServerView', props);
this.focused = true;
this.state = {
sections: []
};
this.data = database.databases.serversDB.objects('servers');
this.data.addListener(this.updateState);
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
async componentWillMount() {
this.props.navigator.setButtons({
rightButtons: [{
id: 'addServer',
icon: iconsMap.add
}]
});
}
componentDidMount() {
this.updateState();
this.jumpToSelectedServer();
}
componentWillReceiveProps(nextProps) {
if (this.props.server !== nextProps.server && nextProps.server && !this.props.login.isRegistering) {
this.timeout = setTimeout(() => {
this.openLogin(nextProps.server);
}, 1000);
}
}
componentWillUnmount() {
this.data.removeAllListeners();
if (this.timeout) {
clearTimeout(this.timeout);
}
}
onNavigatorEvent(event) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'addServer') {
this.props.navigator.push({
screen: 'NewServerView',
title: I18n.t('New_Server')
});
}
} else if (event.type === 'ScreenChangedEvent') {
this.focused = event.id === 'didAppear' || event.id === 'onActivityResumed';
}
}
onPressItem = (item) => {
this.selectAndNavigateTo(item.id);
}
getState = () => {
const sections = [{
title: I18n.t('My_servers'),
data: this.data
}];
return {
...this.state,
sections
};
};
openLogin = (server) => {
if (this.focused) {
this.props.navigator.push({
screen: 'LoginSignupView',
title: server
});
}
}
selectAndNavigateTo = (server) => {
this.props.selectServerRequest(server);
this.openLogin(server);
}
jumpToSelectedServer() {
if (this.props.server && !this.props.login.isRegistering) {
setTimeout(() => {
this.openLogin(this.props.server);
}, 1000);
}
}
updateState = () => {
this.setState(this.getState());
}
renderItem = ({ item }) => (
<Touch
underlayColor='#ccc'
accessibilityTraits='button'
onPress={() => { this.onPressItem(item); }}
>
<View style={styles.serverItem}>
<Text
style={[styles.listItem]}
adjustsFontSizeToFit
>
{item.id}
</Text>
<Fade visible={this.props.server === item.id}>
<Icon
iconSize={24}
size={24}
style={styles.serverChecked}
name='ios-checkmark-circle-outline'
/>
</Fade>
</View>
</Touch>
);
renderSectionHeader = ({ section }) => (
<Text style={styles.headerStyle}>{section.title}</Text>
);
renderSeparator = () => (
<View style={styles.separator} />
);
render() {
return (
<SafeAreaView style={styles.container} testID='list-server-view'>
<SectionList
style={styles.list}
sections={this.state.sections}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyExtractor={item => item.id}
ItemSeparatorComponent={this.renderSeparator}
/>
</SafeAreaView>
);
}
}

View File

@ -281,16 +281,11 @@ export default class LoginSignupView extends LoggedView {
> >
<SafeAreaView style={sharedStyles.container} testID='welcome-view'> <SafeAreaView style={sharedStyles.container} testID='welcome-view'>
<View style={styles.container}> <View style={styles.container}>
<Image
source={require('../static/images/logo.png')}
style={sharedStyles.loginLogo}
resizeMode='center'
/>
<Text style={[sharedStyles.loginText, styles.header, { color: '#81848A' }]}>{I18n.t('Welcome_title_pt_1')}</Text> <Text style={[sharedStyles.loginText, styles.header, { color: '#81848A' }]}>{I18n.t('Welcome_title_pt_1')}</Text>
<Text style={[sharedStyles.loginText, styles.header]}>{I18n.t('Welcome_title_pt_2')}</Text> <Text style={[sharedStyles.loginText, styles.header]}>{I18n.t('Welcome_title_pt_2')}</Text>
<Image <Image
style={styles.planetImage} style={styles.planetImage}
source={require('../static/images/planet.png')} source={require('../static/images/server.png')}
/> />
<Button <Button
title={I18n.t('I_have_an_account')} title={I18n.t('I_have_an_account')}

View File

@ -1,70 +1,137 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text, ScrollView, View, Keyboard, SafeAreaView } from 'react-native'; import { Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, Platform } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { serverRequest, addServer } from '../actions/server'; import { serverRequest } from '../actions/server';
import KeyboardView from '../presentation/KeyboardView'; import sharedStyles from './Styles';
import styles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps'; import scrollPersistTaps from '../utils/scrollPersistTaps';
import Button from '../containers/Button'; import Button from '../containers/Button';
import TextInput from '../containers/TextInput'; import TextInput from '../containers/TextInput';
import Loading from '../containers/Loading';
import LoggedView from './View'; import LoggedView from './View';
import I18n from '../i18n'; import I18n from '../i18n';
import { scale, verticalScale, moderateScale } from '../utils/scaling';
import KeyboardView from '../presentation/KeyboardView';
import { iconsMap } from '../Icons';
const styles = StyleSheet.create({
image: {
alignSelf: 'center',
marginVertical: verticalScale(20)
},
title: {
alignSelf: 'center',
color: '#2F343D',
fontSize: moderateScale(22),
fontWeight: 'bold',
height: verticalScale(28),
lineHeight: verticalScale(28)
},
inputContainer: {
marginTop: scale(20),
marginBottom: scale(20)
},
input: {
color: '#9EA2A8',
fontSize: moderateScale(17),
paddingTop: scale(14),
paddingBottom: scale(14),
paddingHorizontal: scale(16)
}
});
const defaultServer = 'https://open.rocket.chat';
@connect(state => ({ @connect(state => ({
validInstance: !state.server.failure && !state.server.connecting, connecting: state.server.connecting,
validating: state.server.connecting, failure: state.server.failure,
addingServer: state.server.adding currentServer: state.server.server
}), dispatch => ({ }), dispatch => ({
validateServer: url => dispatch(serverRequest(url)), connectServer: (url, adding) => dispatch(serverRequest(url, adding))
addServer: url => dispatch(addServer(url))
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class NewServerView extends LoggedView { export default class NewServerView extends LoggedView {
static propTypes = { static propTypes = {
navigator: PropTypes.object, navigator: PropTypes.object,
validateServer: PropTypes.func.isRequired, server: PropTypes.string,
addServer: PropTypes.func.isRequired, connecting: PropTypes.bool.isRequired,
validating: PropTypes.bool.isRequired, failure: PropTypes.bool.isRequired,
validInstance: PropTypes.bool.isRequired, connectServer: PropTypes.func.isRequired,
addingServer: PropTypes.bool.isRequired previousServer: PropTypes.string,
currentServer: PropTypes.string
} }
constructor(props) { constructor(props) {
super('NewServerView', props); super('NewServerView', props);
this.state = { this.state = {
defaultServer: 'https://open.rocket.chat' text: ''
}; };
props.validateServer(this.state.defaultServer); // Need to call because in case of submit with empty field props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
componentWillMount() {
// if previousServer exists, New Server View is a modal
if (this.props.previousServer) {
const closeButton = {
id: 'close',
testID: 'new-server-close',
title: I18n.t('Close')
};
if (Platform.OS === 'android') {
closeButton.icon = iconsMap.close;
}
this.props.navigator.setButtons({
leftButtons: [closeButton]
});
}
} }
componentDidMount() { componentDidMount() {
const { server } = this.props;
if (server) {
this.props.connectServer(server);
this.setState({ text: server });
} else {
setTimeout(() => { setTimeout(() => {
this.input.focus(); this.input.focus();
}, 600); }, 600);
} }
}
componentWillReceiveProps(nextProps) {
if (nextProps.failure && nextProps.failure !== this.props.failure) {
Alert.alert(I18n.t('Oops'), I18n.t('The_URL_is_invalid'));
}
}
onNavigatorEvent(event) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'close') {
const {
navigator, connectServer, previousServer, currentServer
} = this.props;
navigator.dismissModal();
if (previousServer !== currentServer) {
connectServer(previousServer);
}
}
}
}
onChangeText = (text) => { onChangeText = (text) => {
this.setState({ text }); this.setState({ text });
this.props.validateServer(this.completeUrl(text));
} }
submit = () => { submit = () => {
if (this.props.validInstance) { if (this.state.text) {
Keyboard.dismiss(); Keyboard.dismiss();
this.props.addServer(this.completeUrl(this.state.text)); this.props.connectServer(this.completeUrl(this.state.text));
} }
} }
completeUrl = (url) => { completeUrl = (url) => {
url = url && url.trim(); url = url && url.trim();
if (!url) {
return this.state.defaultServer;
}
if (/^(\w|[0-9-_]){3,}$/.test(url) && if (/^(\w|[0-9-_]){3,}$/.test(url) &&
/^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) { /^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) {
url = `${ url }.rocket.chat`; url = `${ url }.rocket.chat`;
@ -81,60 +148,39 @@ export default class NewServerView extends LoggedView {
return url.replace(/\/+$/, ''); return url.replace(/\/+$/, '');
} }
renderValidation = () => {
if (this.props.validating) {
return (
<Text style={[styles.validateText, styles.validatingText]}>
{I18n.t('Validating')} {this.state.text || 'open'} ...
</Text>
);
}
if (this.props.validInstance) {
return (
<Text style={[styles.validateText, styles.validText]}>
{this.state.url} {I18n.t('is_a_valid_RocketChat_instance')}
</Text>
);
}
return (
<Text style={[styles.validateText, styles.invalidText]}>
{this.state.url} {I18n.t('is_not_a_valid_RocketChat_instance')}
</Text>
);
}
render() { render() {
const { validInstance } = this.props; const { connecting } = this.props;
const { text } = this.state;
return ( return (
<KeyboardView <KeyboardView
contentContainerStyle={styles.container} contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
key='login-view'
> >
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}> <ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView style={styles.container} testID='new-server-view'> <SafeAreaView style={sharedStyles.container} testID='new-server-view'>
<Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_in_your_server')}</Text> <Image style={styles.image} source={require('../static/images/server.png')} />
<Text style={styles.title}>{I18n.t('Sign_in_your_server')}</Text>
<TextInput <TextInput
inputRef={e => this.input = e} inputRef={e => this.input = e}
containerStyle={{ marginBottom: 5 }} containerStyle={styles.inputContainer}
label={I18n.t('Your_server')} inputStyle={styles.input}
placeholder={this.state.defaultServer} placeholder={defaultServer}
value={text}
returnKeyType='done' returnKeyType='done'
onChangeText={this.onChangeText} onChangeText={this.onChangeText}
testID='new-server-view-input' testID='new-server-view-input'
onSubmitEditing={this.submit} onSubmitEditing={this.submit}
clearButtonMode='while-editing'
/> />
{this.renderValidation()}
<View style={[styles.alignItemsFlexStart, { marginTop: 20 }]}>
<Button <Button
title={I18n.t('Connect')} title={I18n.t('Connect')}
type='primary' type='primary'
onPress={this.submit} onPress={this.submit}
disabled={!validInstance} disabled={text.length === 0}
loading={connecting}
testID='new-server-view-button' testID='new-server-view-button'
/> />
</View>
<Loading visible={this.props.addingServer} />
</SafeAreaView> </SafeAreaView>
</ScrollView> </ScrollView>
</KeyboardView> </KeyboardView>

View File

@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import I18n from '../i18n'; import I18n from '../i18n';
import { iconsMap } from '../Icons';
const userAgentAndroid = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'; const userAgentAndroid = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1';
const userAgent = Platform.OS === 'ios' ? 'UserAgent' : userAgentAndroid; const userAgent = Platform.OS === 'ios' ? 'UserAgent' : userAgentAndroid;
@ -16,7 +17,8 @@ export default class TermsServiceView extends React.PureComponent {
static navigatorButtons = { static navigatorButtons = {
leftButtons: [{ leftButtons: [{
id: 'close', id: 'close',
title: I18n.t('Close') title: I18n.t('Close'),
icon: Platform.OS === 'android' ? iconsMap.close : undefined
}] }]
} }

View File

@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text, TouchableWithoutFeedback, Image } from 'react-native';
import styles from './styles';
export default class Button extends React.PureComponent {
static propTypes = {
title: PropTypes.string,
subtitle: PropTypes.string,
type: PropTypes.string,
icon: PropTypes.node.isRequired,
testID: PropTypes.string.isRequired,
onPress: PropTypes.func
}
static defaultProps = {
title: 'Press me!',
type: 'primary',
onPress: () => alert('It works!')
}
state = {
active: false
};
render() {
const {
title, subtitle, type, onPress, icon, testID
} = this.props;
const { active } = this.state;
const activeStyle = active && styles.buttonActive;
return (
<TouchableWithoutFeedback
onPress={onPress}
onPressIn={() => this.setState({ active: true })}
onPressOut={() => this.setState({ active: false })}
testID={testID}
>
<View style={[styles.buttonContainer, styles[`button_container_${ type }`]]}>
<View style={styles.buttonIconContainer}>
{icon}
</View>
<View style={styles.buttonCenter}>
<Text style={[styles.buttonTitle, styles[`button_text_${ type }`], activeStyle]}>{title}</Text>
{subtitle ? <Text style={[styles.buttonSubtitle, activeStyle]}>{subtitle}</Text> : null}
</View>
{type === 'secondary' ? <Image source={require('../../static/images/disclosureIndicator.png')} style={styles.buttonIcon} /> : null}
</View>
</TouchableWithoutFeedback>
);
}
}

View File

@ -0,0 +1,79 @@
import React from 'react';
import { View, Text, Image, SafeAreaView } from 'react-native';
import PropTypes from 'prop-types';
import I18n from '../../i18n';
import openLink from '../../utils/openLink';
import Button from './Button';
import styles from './styles';
import LoggedView from '../View';
/** @extends React.Component */
export default class OnboardingView extends LoggedView {
static propTypes = {
navigator: PropTypes.object
}
constructor(props) {
super('CreateChannelView', props);
}
connectServer = () => {
this.props.navigator.push({
screen: 'NewServerView',
navigatorStyle: {
navBarHidden: true
}
});
}
joinCommunity = () => {
this.props.navigator.push({
screen: 'NewServerView',
passProps: {
server: 'https://open.rocket.chat'
},
navigatorStyle: {
navBarHidden: true
}
});
}
createWorkspace = () => {
openLink('https://cloud.rocket.chat/trial');
}
render() {
return (
<SafeAreaView style={styles.container} testID='onboarding-view'>
<Image style={styles.onboarding} source={require('../../static/images/onboarding.png')} />
<Text style={styles.title}>{I18n.t('Welcome_to_RocketChat')}</Text>
<Text style={styles.subtitle}>{I18n.t('Open_Source_Communication')}</Text>
<View style={styles.buttonsContainer}>
<Button
type='secondary'
title={I18n.t('Connect_to_a_server')}
icon={<Image source={require('../../static/images/connectServer.png')} />}
onPress={this.connectServer}
testID='connect-server-button'
/>
<Button
type='secondary'
title={I18n.t('Join_the_community')}
subtitle='open.rocket.chat'
icon={<Image source={require('../../static/images/logoSmall.png')} />}
onPress={this.joinCommunity}
testID='join-community-button'
/>
<Button
type='primary'
title={I18n.t('Create_a_new_workspace')}
icon={<Image source={require('../../static/images/plusWhite.png')} />}
onPress={this.createWorkspace}
testID='create-workspace-button'
/>
</View>
</SafeAreaView>
);
}
}

View File

@ -0,0 +1,98 @@
import { StyleSheet } from 'react-native';
import { verticalScale, scale, moderateScale } from '../../utils/scaling';
const colors = {
backgroundPrimary: '#1D74F5',
backgroundSecondary: 'white',
textColorPrimary: 'white',
textColorSecondary: '#1D74F5',
borderColorPrimary: '#1D74F5',
borderColorSecondary: '#E1E5E8'
};
export default StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff'
},
onboarding: {
alignSelf: 'center',
paddingHorizontal: scale(45),
marginTop: verticalScale(30),
marginBottom: verticalScale(50),
maxHeight: verticalScale(250),
resizeMode: 'contain'
},
title: {
alignSelf: 'center',
color: '#2F343D',
fontSize: moderateScale(24),
height: moderateScale(28),
lineHeight: moderateScale(28),
fontWeight: 'bold'
},
subtitle: {
alignSelf: 'center',
color: '#54585E',
fontSize: moderateScale(16),
height: moderateScale(20),
lineHeight: moderateScale(20),
fontWeight: 'normal'
},
buttonsContainer: {
marginBottom: verticalScale(10),
marginTop: verticalScale(30)
},
buttonContainer: {
marginHorizontal: scale(15),
marginVertical: scale(5),
flexDirection: 'row',
height: verticalScale(60),
alignItems: 'center',
borderWidth: StyleSheet.hairlineWidth,
borderRadius: moderateScale(2)
},
buttonCenter: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
},
buttonTitle: {
fontSize: moderateScale(16),
fontWeight: '600'
},
buttonSubtitle: {
color: '#9EA2A8',
fontSize: moderateScale(14),
height: moderateScale(18)
},
buttonIconContainer: {
width: 65,
alignItems: 'center',
justifyContent: 'center'
},
buttonIcon: {
marginHorizontal: scale(20)
},
buttonActive: {
opacity: 0.5
},
button_container_primary: {
backgroundColor: colors.backgroundPrimary,
borderColor: colors.borderColorPrimary
},
button_container_secondary: {
backgroundColor: colors.backgroundSecondary,
borderColor: colors.borderColorSecondary
},
button_text_primary: {
color: colors.textColorPrimary
},
button_text_secondary: {
color: colors.textColorSecondary
}
});

View File

@ -17,6 +17,7 @@ import { leaveRoom } from '../../actions/room';
import log from '../../utils/log'; import log from '../../utils/log';
import RoomTypeIcon from '../../containers/RoomTypeIcon'; import RoomTypeIcon from '../../containers/RoomTypeIcon';
import I18n from '../../i18n'; import I18n from '../../i18n';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
const renderSeparator = () => <View style={styles.separator} />; const renderSeparator = () => <View style={styles.separator} />;
const getRoomTitle = room => (room.t === 'd' ? <Text>{room.fname}</Text> : <Text><RoomTypeIcon type={room.t} />&nbsp;{room.name}</Text>); const getRoomTitle = room => (room.t === 'd' ? <Text>{room.fname}</Text> : <Text><RoomTypeIcon type={room.t} />&nbsp;{room.name}</Text>);
@ -122,13 +123,13 @@ export default class RoomActionsView extends LoggedView {
}, { }, {
data: [ data: [
{ {
icon: 'ios-call-outline', icon: 'ios-call',
name: I18n.t('Voice_call'), name: I18n.t('Voice_call'),
disabled: true, disabled: true,
testID: 'room-actions-voice' testID: 'room-actions-voice'
}, },
{ {
icon: 'ios-videocam-outline', icon: 'ios-videocam',
name: I18n.t('Video_call'), name: I18n.t('Video_call'),
disabled: true, disabled: true,
testID: 'room-actions-video' testID: 'room-actions-video'
@ -145,14 +146,14 @@ export default class RoomActionsView extends LoggedView {
testID: 'room-actions-files' testID: 'room-actions-files'
}, },
{ {
icon: 'ios-at-outline', icon: 'ios-at',
name: I18n.t('Mentions'), name: I18n.t('Mentions'),
route: 'MentionedMessagesView', route: 'MentionedMessagesView',
params: { rid }, params: { rid },
testID: 'room-actions-mentioned' testID: 'room-actions-mentioned'
}, },
{ {
icon: 'ios-star-outline', icon: 'ios-star',
name: I18n.t('Starred'), name: I18n.t('Starred'),
route: 'StarredMessagesView', route: 'StarredMessagesView',
params: { rid }, params: { rid },
@ -166,7 +167,7 @@ export default class RoomActionsView extends LoggedView {
testID: 'room-actions-search' testID: 'room-actions-search'
}, },
{ {
icon: 'ios-share-outline', icon: 'ios-share',
name: I18n.t('Share'), name: I18n.t('Share'),
disabled: true, disabled: true,
testID: 'room-actions-share' testID: 'room-actions-share'
@ -186,7 +187,7 @@ export default class RoomActionsView extends LoggedView {
testID: 'room-actions-snippeted' testID: 'room-actions-snippeted'
}, },
{ {
icon: `ios-notifications${ notifications ? '' : '-off' }-outline`, icon: `ios-notifications${ notifications ? '' : '-off' }`,
name: I18n.t(`${ notifications ? 'Enable' : 'Disable' }_notifications`), name: I18n.t(`${ notifications ? 'Enable' : 'Disable' }_notifications`),
event: () => this.toggleNotifications(), event: () => this.toggleNotifications(),
testID: 'room-actions-notifications' testID: 'room-actions-notifications'
@ -403,6 +404,7 @@ export default class RoomActionsView extends LoggedView {
ItemSeparatorComponent={renderSeparator} ItemSeparatorComponent={renderSeparator}
keyExtractor={item => item.name} keyExtractor={item => item.name}
testID='room-actions-list' testID='room-actions-list'
{...scrollPersistTaps}
/> />
</SafeAreaView> </SafeAreaView>
); );

View File

@ -72,7 +72,7 @@ export default class RoomView extends LoggedView {
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
} }
async componentWillMount() { componentWillMount() {
this.props.navigator.setButtons({ this.props.navigator.setButtons({
rightButtons: [{ rightButtons: [{
id: 'more', id: 'more',
@ -267,7 +267,7 @@ export default class RoomView extends LoggedView {
</View> </View>
); );
} }
return <MessageBox onSubmit={this.sendMessage} rid={this.rid} />; return <MessageBox key='room-view-messagebox' onSubmit={this.sendMessage} rid={this.rid} />;
}; };
renderHeader = () => { renderHeader = () => {
@ -282,6 +282,7 @@ export default class RoomView extends LoggedView {
return <ActivityIndicator style={styles.loading} />; return <ActivityIndicator style={styles.loading} />;
} }
return ( return (
[
<List <List
key='room-view-messages' key='room-view-messages'
end={this.state.end} end={this.state.end}
@ -289,7 +290,9 @@ export default class RoomView extends LoggedView {
renderFooter={this.renderHeader} renderFooter={this.renderHeader}
onEndReached={this.onEndReached} onEndReached={this.onEndReached}
renderRow={this.renderItem} renderRow={this.renderItem}
/> />,
this.renderFooter()
]
); );
} }
@ -297,7 +300,6 @@ export default class RoomView extends LoggedView {
return ( return (
<SafeAreaView style={styles.container} testID='room-view'> <SafeAreaView style={styles.container} testID='room-view'>
{this.renderList()} {this.renderList()}
{this.renderFooter()}
{this.state.room._id && this.props.showActions ? {this.state.room._id && this.props.showActions ?
<MessageActions room={this.state.room} user={this.props.user} /> : <MessageActions room={this.state.room} user={this.props.user} /> :
null} null}

View File

@ -8,7 +8,7 @@ export default StyleSheet.create({
flex: 1 flex: 1
}, },
containerScrollView: { containerScrollView: {
padding: 20 padding: 15
}, },
label: { label: {
lineHeight: 40, lineHeight: 40,
@ -191,11 +191,6 @@ export default StyleSheet.create({
fontSize: 20, fontSize: 20,
marginBottom: 20 marginBottom: 20
}, },
loginLogo: {
width: 50,
height: 50,
marginVertical: 25
},
headerButton: { headerButton: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
height: 44, height: 44,

View File

@ -3,12 +3,12 @@ import { Provider } from 'react-redux';
import CreateChannelView from './CreateChannelView'; import CreateChannelView from './CreateChannelView';
import ForgotPasswordView from './ForgotPasswordView'; import ForgotPasswordView from './ForgotPasswordView';
import ListServerView from './ListServerView';
import LoginSignupView from './LoginSignupView'; import LoginSignupView from './LoginSignupView';
import LoginView from './LoginView'; import LoginView from './LoginView';
import MentionedMessagesView from './MentionedMessagesView'; import MentionedMessagesView from './MentionedMessagesView';
import NewServerView from './NewServerView'; import NewServerView from './NewServerView';
import OAuthView from './OAuthView'; import OAuthView from './OAuthView';
import OnboardingView from './OnboardingView';
import PinnedMessagesView from './PinnedMessagesView'; import PinnedMessagesView from './PinnedMessagesView';
import PrivacyPolicyView from './PrivacyPolicyView'; import PrivacyPolicyView from './PrivacyPolicyView';
import ProfileView from './ProfileView'; import ProfileView from './ProfileView';
@ -32,12 +32,12 @@ import TermsServiceView from './TermsServiceView';
export const registerScreens = (store) => { export const registerScreens = (store) => {
Navigation.registerComponent('CreateChannelView', () => CreateChannelView, store, Provider); Navigation.registerComponent('CreateChannelView', () => CreateChannelView, store, Provider);
Navigation.registerComponent('ForgotPasswordView', () => ForgotPasswordView, store, Provider); Navigation.registerComponent('ForgotPasswordView', () => ForgotPasswordView, store, Provider);
Navigation.registerComponent('ListServerView', () => ListServerView, store, Provider);
Navigation.registerComponent('LoginSignupView', () => LoginSignupView, store, Provider); Navigation.registerComponent('LoginSignupView', () => LoginSignupView, store, Provider);
Navigation.registerComponent('LoginView', () => LoginView, store, Provider); Navigation.registerComponent('LoginView', () => LoginView, store, Provider);
Navigation.registerComponent('MentionedMessagesView', () => MentionedMessagesView, store, Provider); Navigation.registerComponent('MentionedMessagesView', () => MentionedMessagesView, store, Provider);
Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider);
Navigation.registerComponent('OAuthView', () => OAuthView, store, Provider); Navigation.registerComponent('OAuthView', () => OAuthView, store, Provider);
Navigation.registerComponent('OnboardingView', () => OnboardingView, store, Provider);
Navigation.registerComponent('PinnedMessagesView', () => PinnedMessagesView, store, Provider); Navigation.registerComponent('PinnedMessagesView', () => PinnedMessagesView, store, Provider);
Navigation.registerComponent('PrivacyPolicyView', () => PrivacyPolicyView, store, Provider); Navigation.registerComponent('PrivacyPolicyView', () => PrivacyPolicyView, store, Provider);
Navigation.registerComponent('ProfileView', () => ProfileView, store, Provider); Navigation.registerComponent('ProfileView', () => ProfileView, store, Provider);

View File

@ -1,50 +0,0 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
describe('Add server', () => {
before(async() => {
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', async() => {
it('should have add server screen', async() => {
await expect(element(by.id('new-server-view'))).toBeVisible();
});
it('should have server input', async() => {
await expect(element(by.id('new-server-view-input'))).toBeVisible();
});
it('should have submit button', async() => {
await expect(element(by.id('new-server-view-button'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
it('should insert "invalidtest" and get an invalid instance', async() => {
await element(by.id('new-server-view-input')).replaceText('invalidtest');
await waitFor(element(by.text(' is not a valid Rocket.Chat instance'))).toBeVisible().withTimeout(60000);
await expect(element(by.text(' is not a valid Rocket.Chat instance'))).toBeVisible();
});
it('should have a button to add a new server', async() => {
await element(by.id('new-server-view-input')).replaceText(data.server);
await waitFor(element(by.text(' is a valid Rocket.Chat instance'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-server-view-button'))).toBeVisible();
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});
});

76
e2e/00-onboarding.spec.js Normal file
View File

@ -0,0 +1,76 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
describe('Onboarding', () => {
before(async() => {
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
});
describe('Render', async() => {
it('should have onboarding screen', async() => {
await expect(element(by.id('onboarding-view'))).toBeVisible();
});
it('should have "Connect to a server"', async() => {
await expect(element(by.id('connect-server-button'))).toBeVisible();
});
it('should have "Join the community"', async() => {
await expect(element(by.id('join-community-button'))).toBeVisible();
});
it('should have "Create a new workspace"', async() => {
await expect(element(by.id('create-workspace-button'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
});
describe('Usage', async() => {
it('should navigate to create new workspace', async() => {
// webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
});
it('should navigate to join community', async() => {
await element(by.id('join-community-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
await waitFor(element(by.text('https://open.rocket.chat'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('https://open.rocket.chat'))).toBeVisible();
});
it('should navigate to new server', async() => {
await device.reloadReactNative();
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('connect-server-button')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-server-view'))).toBeVisible();
});
it('should enter an invalid server and get error', async() => {
await element(by.id('new-server-view-input')).replaceText('invalidtest');
await element(by.id('new-server-view-button')).tap();
const errorText = 'The URL you entered is invalid. Check it and try again, please!';
await waitFor(element(by.text(errorText))).toBeVisible().withTimeout(60000);
await expect(element(by.text(errorText))).toBeVisible();
});
it('should enter a valid server and navigate to welcome', async() => {
await element(by.text('OK')).tap();
await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
});
afterEach(async() => {
takeScreenshot();
});
});
});

View File

@ -5,16 +5,17 @@ const { takeScreenshot } = require('./helpers/screenshot');
const { logout, sleep } = require('./helpers/app'); const { logout, sleep } = require('./helpers/app');
const data = require('./data'); const data = require('./data');
async function navigateToRegister() {
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await element(by.id('welcome-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
}
describe('Create user screen', () => { describe('Create user screen', () => {
before(async() => { before(async() => {
await device.reloadReactNative(); await device.reloadReactNative();
await navigateToRegister(); await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('connect-server-button')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.server);
await element(by.id('new-server-view-button')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await element(by.id('welcome-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
}); });
describe('Render', () => { describe('Render', () => {

View File

@ -63,7 +63,8 @@ describe('Login screen', () => {
await tapBack('Welcome'); await tapBack('Welcome');
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible(); await expect(element(by.id('welcome-view'))).toBeVisible();
await navigateToLogin(); await element(by.id('welcome-view-login')).tap();
await expect(element(by.id('login-view'))).toBeVisible();
}); });
it('should insert wrong password and get error', async() => { it('should insert wrong password and get error', async() => {

View File

@ -6,9 +6,9 @@ const { login, navigateToLogin, tapBack } = require('./helpers/app');
const data = require('./data'); const data = require('./data');
describe('Rooms list screen', () => { describe('Rooms list screen', () => {
before(async() => { // before(async() => {
await device.reloadReactNative(); // TODO: remove this after fix logout subscription // await device.reloadReactNative(); // TODO: remove this after fix logout subscription
}); // });
describe('Render', async() => { describe('Render', async() => {
it('should have rooms list screen', async() => { it('should have rooms list screen', async() => {
@ -71,7 +71,7 @@ describe('Rooms list screen', () => {
await element(by.id('sidebar-add-server')).tap(); await element(by.id('sidebar-add-server')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('new-server-view'))).toBeVisible(); await expect(element(by.id('new-server-view'))).toBeVisible();
await tapBack('Messages'); await element(by.text('Close')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible(); await expect(element(by.id('rooms-list-view'))).toBeVisible();
}); });
@ -81,15 +81,18 @@ describe('Rooms list screen', () => {
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('sidebar-logout'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('sidebar-logout'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-logout')).tap(); await element(by.id('sidebar-logout')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000); await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible(); await expect(element(by.id('onboarding-view'))).toBeVisible();
await navigateToLogin();
await login();
}); });
}); });
afterEach(async() => { afterEach(async() => {
takeScreenshot(); takeScreenshot();
}); });
after(async() => {
await navigateToLogin();
await login();
})
}); });
}); });

View File

@ -7,7 +7,7 @@ const { tapBack } = require('./helpers/app');
describe('Create room screen', () => { describe('Create room screen', () => {
before(async() => { before(async() => {
await device.reloadReactNative(); // TODO: remove this after fix logout subscription // await device.reloadReactNative(); // TODO: remove this after fix logout subscription
await element(by.id('rooms-list-view-create-channel')).tap(); await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
}); });

View File

@ -149,7 +149,7 @@ describe('Room screen', () => {
await element(by.id('messagebox-input')).typeText('#general'); await element(by.id('messagebox-input')).typeText('#general');
await waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(60000); await waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('messagebox-container'))).toBeVisible(); await expect(element(by.id('messagebox-container'))).toBeVisible();
await element(by.id('mention-item-general')).tap(); await element(by.id('mention-item-general')).atIndex(0).tap();
await expect(element(by.id('messagebox-input'))).toHaveText('#general '); await expect(element(by.id('messagebox-input'))).toHaveText('#general ');
await element(by.id('messagebox-input')).clearText(); await element(by.id('messagebox-input')).clearText();
}); });

View File

@ -289,22 +289,21 @@ describe('Room actions screen', () => {
describe('Add User', async() => { describe('Add User', async() => {
it('should add user to the room', async() => { it('should add user to the room', async() => {
const user = 'detoxrn';
await waitFor(element(by.id('room-actions-add-user'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'up'); await waitFor(element(by.id('room-actions-add-user'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'up');
await element(by.id('room-actions-add-user')).tap(); await element(by.id('room-actions-add-user')).tap();
await element(by.id('select-users-view-search')).tap(); await element(by.id('select-users-view-search')).tap();
await element(by.id('select-users-view-search')).replaceText(user); await element(by.id('select-users-view-search')).replaceText(data.alternateUser);
await waitFor(element(by.id(`select-users-view-item-${ user }`))).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`select-users-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`select-users-view-item-${ user }`))).toBeVisible(); await expect(element(by.id(`select-users-view-item-${ data.alternateUser }`))).toBeVisible();
await element(by.id(`select-users-view-item-${ user }`)).tap(); await element(by.id(`select-users-view-item-${ data.alternateUser }`)).tap();
await waitFor(element(by.id(`selected-user-${ user }`))).toBeVisible().withTimeout(5000); await waitFor(element(by.id(`selected-user-${ data.alternateUser }`))).toBeVisible().withTimeout(5000);
await expect(element(by.id(`selected-user-${ user }`))).toBeVisible(); await expect(element(by.id(`selected-user-${ data.alternateUser }`))).toBeVisible();
await element(by.id('selected-users-view-submit')).tap(); await element(by.id('selected-users-view-submit')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await element(by.id('room-actions-members')).tap(); await element(by.id('room-actions-members')).tap();
await element(by.id('room-members-view-toggle-status')).tap(); await element(by.id('room-members-view-toggle-status')).tap();
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible(); await expect(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible();
await backToActions(); await backToActions();
}); });
@ -314,7 +313,6 @@ describe('Room actions screen', () => {
}); });
describe('Room Members', async() => { describe('Room Members', async() => {
const user = 'detoxrn';
before(async() => { before(async() => {
await element(by.id('room-actions-members')).tap(); await element(by.id('room-actions-members')).tap();
await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000); await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000);
@ -323,24 +321,24 @@ describe('Room actions screen', () => {
it('should show all users', async() => { it('should show all users', async() => {
await element(by.id('room-members-view-toggle-status')).tap(); await element(by.id('room-members-view-toggle-status')).tap();
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible(); await expect(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible();
}); });
it('should filter user', async() => { it('should filter user', async() => {
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible(); await expect(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible();
await element(by.id('room-members-view-search')).replaceText('rocket'); await element(by.id('room-members-view-search')).replaceText('rocket');
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeNotVisible().withTimeout(60000); await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeNotVisible(); await expect(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeNotVisible();
await element(by.id('room-members-view-search')).tap(); await element(by.id('room-members-view-search')).tap();
await element(by.id('room-members-view-search')).clearText(''); await element(by.id('room-members-view-search')).clearText('');
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ user }`))).toBeVisible(); await expect(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible();
}); });
it('should mute user', async() => { it('should mute user', async() => {
await element(by.id(`room-members-view-item-${ user }`)).longPress(); await element(by.id(`room-members-view-item-${ data.alternateUser }`)).longPress();
await waitFor(element(by.text('Mute'))).toBeVisible().withTimeout(5000); await waitFor(element(by.text('Mute'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Mute'))).toBeVisible(); await expect(element(by.text('Mute'))).toBeVisible();
await element(by.text('Mute')).tap(); await element(by.text('Mute')).tap();
@ -348,7 +346,7 @@ describe('Room actions screen', () => {
await expect(element(by.text('User has been muted!'))).toBeVisible(); await expect(element(by.text('User has been muted!'))).toBeVisible();
await waitFor(element(by.text('User has been muted!'))).toBeNotVisible().withTimeout(60000); await waitFor(element(by.text('User has been muted!'))).toBeNotVisible().withTimeout(60000);
await expect(element(by.text('User has been muted!'))).toBeNotVisible(); await expect(element(by.text('User has been muted!'))).toBeNotVisible();
await element(by.id(`room-members-view-item-${ user }`)).longPress(); await element(by.id(`room-members-view-item-${ data.alternateUser }`)).longPress();
await waitFor(element(by.text('Unmute'))).toBeVisible().withTimeout(2000); await waitFor(element(by.text('Unmute'))).toBeVisible().withTimeout(2000);
await expect(element(by.text('Unmute'))).toBeVisible(); await expect(element(by.text('Unmute'))).toBeVisible();
await element(by.text('Unmute')).tap(); await element(by.text('Unmute')).tap();
@ -359,12 +357,12 @@ describe('Room actions screen', () => {
}); });
it('should navigate to direct room', async() => { it('should navigate to direct room', async() => {
await waitFor(element(by.id(`room-members-view-item-${ user }`))).toExist().withTimeout(5000); await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toExist().withTimeout(5000);
await element(by.id(`room-members-view-item-${ user }`)).tap(); await element(by.id(`room-members-view-item-${ data.alternateUser }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('room-view'))).toBeVisible(); await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.text(user))).toBeVisible().withTimeout(60000); await waitFor(element(by.text(data.alternateUser))).toBeVisible().withTimeout(60000);
await expect(element(by.text(user))).toBeVisible(); await expect(element(by.text(data.alternateUser))).toBeVisible();
await tapBack('Messages'); await tapBack('Messages');
await waitFor(element(by.id('room-list-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('room-list-view'))).toBeVisible().withTimeout(2000);
}); });

View File

@ -7,6 +7,7 @@ const data = require('./data');
describe('Change server', () => { describe('Change server', () => {
before(async() => { before(async() => {
await device.reloadReactNative(); await device.reloadReactNative();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
}); });
it('should add server and create new user', async() => { it('should add server and create new user', async() => {
@ -16,10 +17,9 @@ describe('Change server', () => {
await element(by.id('sidebar-toggle-server')).tap(); await element(by.id('sidebar-toggle-server')).tap();
await waitFor(element(by.id('sidebar-add-server'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('sidebar-add-server'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-add-server')).tap(); await element(by.id('sidebar-add-server')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
// Add server // Add server
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.alternateServer); await element(by.id('new-server-view-input')).replaceText(data.alternateServer);
await waitFor(element(by.text(' is a valid Rocket.Chat instance'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-button')).tap(); await element(by.id('new-server-view-button')).tap();
// Navigate to register // Navigate to register
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
@ -36,7 +36,6 @@ describe('Change server', () => {
await element(by.id('register-view-submit-username')).tap(); await element(by.id('register-view-submit-username')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible(); await expect(element(by.id('rooms-list-view'))).toBeVisible();
// await expect(element(by.id('rooms-list-view-sidebar'))).toHaveLabel(`Connected to ${ data.alternateServer }. Tap to view servers list.`);
// For a sanity test, to make sure roomslist is showing correct rooms // For a sanity test, to make sure roomslist is showing correct rooms
// app CANNOT show public room created on previous tests // app CANNOT show public room created on previous tests
await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible().withTimeout(60000); await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible().withTimeout(60000);

View File

@ -89,8 +89,8 @@ describe('Broadcast room', () => {
it('should tap on reply button and navigate to direct room', async() => { it('should tap on reply button and navigate to direct room', async() => {
await element(by.text('Reply')).tap(); await element(by.text('Reply')).tap();
await waitFor(element(by.text(data.user))).toBeVisible().withTimeout(60000); await waitFor(element(by.text(data.user)).atIndex(0)).toBeVisible().withTimeout(60000);
await expect(element(by.text(data.user))).toBeVisible(); await expect(element(by.text(data.user)).atIndex(0)).toBeVisible();
}); });
it('should reply broadcasted message', async() => { it('should reply broadcasted message', async() => {

View File

@ -86,7 +86,7 @@ describe('Profile screen', () => {
}); });
it('should change email and password', async() => { it('should change email and password', async() => {
await element(by.id('profile-view-email')).replaceText(`test${ data.email }`); await element(by.id('profile-view-email')).replaceText(`diego.mello+e2e${ data.random }test@rocket.chat`);
await element(by.id('profile-view-new-password')).replaceText(`${ data.password }new`); await element(by.id('profile-view-new-password')).replaceText(`${ data.password }new`);
await element(by.id('profile-view-submit')).tap(); await element(by.id('profile-view-submit')).tap();
await waitFor(element(by.id('profile-view-typed-password'))).toBeVisible().withTimeout(10000); await waitFor(element(by.id('profile-view-typed-password'))).toBeVisible().withTimeout(10000);

View File

@ -1,13 +1,13 @@
const random = require('./helpers/random'); const random = require('./helpers/random');
const value = random(20); const value = random(20);
const data = { const data = {
server: 'https://unstable.rocket.chat', server: 'https://stable.rocket.chat',
alternateServer: 'https://stable.rocket.chat', alternateServer: 'https://open.rocket.chat',
user: `user${ value }`, user: `user${ value }`,
password: `password${ value }`, password: `password${ value }`,
alternateUser: 'detoxrn', alternateUser: 'detox',
alternateUserPassword: '123', alternateUserPassword: '123',
email: `detoxrn+${ value }@rocket.chat`, email: `diego.mello+e2e${ value }@rocket.chat`,
random: value random: value
} }
module.exports = data; module.exports = data;

View File

@ -4,14 +4,16 @@ const {
const data = require('../data'); const data = require('../data');
async function addServer() { async function addServer() {
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await element(by.id('connect-server-button')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-server-view'))).toBeVisible();
await element(by.id('new-server-view-input')).replaceText(data.server); await element(by.id('new-server-view-input')).replaceText(data.server);
await waitFor(element(by.text(' is a valid Rocket.Chat instance'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('new-server-view-button'))).toBeVisible().withTimeout(2000);
await element(by.id('new-server-view-button')).tap(); await element(by.id('new-server-view-button')).tap();
} }
async function navigateToLogin() { async function navigateToLogin() {
await addServer();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await element(by.id('welcome-view-login')).tap(); await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
@ -30,8 +32,8 @@ async function logout() {
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('sidebar-logout'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('sidebar-logout'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-logout')).tap(); await element(by.id('sidebar-logout')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible(); await expect(element(by.id('onboarding-view'))).toBeVisible();
} }
async function tapBack(label) { async function tapBack(label) {

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

1242
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,17 +25,18 @@
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.0.0-beta.47", "@babel/polyfill": "^7.0.0-beta.47",
"@remobile/react-native-toast": "^1.0.7", "@remobile/react-native-toast": "^1.0.7",
"@storybook/addons": "^3.4.8", "@storybook/addons": "^3.4.10",
"@storybook/react-native": "^3.4.8", "@storybook/react-native": "^3.4.10",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"ejson": "^2.1.2", "ejson": "^2.1.2",
"js-base64": "^2.4.6", "js-base64": "^2.4.8",
"js-sha256": "^0.9.0", "js-sha256": "^0.9.0",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"markdown-it-flowdock": "^0.3.7", "markdown-it-flowdock": "^0.3.7",
"moment": "^2.22.2", "moment": "^2.22.2",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.1", "react": "^16.4.2",
"react-clone-referenced-element": "^1.0.1",
"react-emojione": "^5.0.0", "react-emojione": "^5.0.0",
"react-native": "^0.56.0", "react-native": "^0.56.0",
"react-native-actionsheet": "^2.4.2", "react-native-actionsheet": "^2.4.2",
@ -43,51 +44,51 @@
"react-native-dialog": "^5.1.0", "react-native-dialog": "^5.1.0",
"react-native-fabric": "^0.5.1", "react-native-fabric": "^0.5.1",
"react-native-fast-image": "^4.0.14", "react-native-fast-image": "^4.0.14",
"react-native-i18n": "^2.0.14", "react-native-i18n": "^2.0.15",
"react-native-image-crop-picker": "git+https://github.com/RocketChat/react-native-image-crop-picker.git", "react-native-image-crop-picker": "git+https://github.com/RocketChat/react-native-image-crop-picker.git",
"react-native-image-zoom-viewer": "^2.2.13", "react-native-image-zoom-viewer": "^2.2.14",
"react-native-keyboard-aware-scroll-view": "^0.6.0", "react-native-keyboard-aware-scroll-view": "^0.6.0",
"react-native-keyboard-input": "^5.2.3", "react-native-keyboard-input": "^5.2.3",
"react-native-keyboard-tracking-view": "^5.4.4", "react-native-keyboard-tracking-view": "^5.4.4",
"react-native-markdown-renderer": "^3.2.8", "react-native-markdown-renderer": "^3.2.8",
"react-native-meteor": "^1.4.0", "react-native-meteor": "^1.4.0",
"react-native-modal": "^6.4.0", "react-native-modal": "^6.5.0",
"react-native-navigation": "^1.1.474", "react-native-navigation": "^1.1.479",
"react-native-notifications": "git+https://github.com/RocketChat/react-native-notifications.git", "react-native-notifications": "git+https://github.com/RocketChat/react-native-notifications.git",
"react-native-optimized-flatlist": "^1.0.4", "react-native-optimized-flatlist": "^1.0.4",
"react-native-picker-select": "^4.0.0", "react-native-picker-select": "^4.2.0",
"react-native-responsive-ui": "^1.1.1", "react-native-responsive-ui": "^1.1.1",
"react-native-safari-view": "^2.1.0", "react-native-safari-view": "^2.1.0",
"react-native-scrollable-tab-view": "git+https://github.com/skv-headless/react-native-scrollable-tab-view.git", "react-native-scrollable-tab-view": "git+https://github.com/skv-headless/react-native-scrollable-tab-view.git",
"react-native-slider": "^0.11.0", "react-native-slider": "^0.11.0",
"react-native-svg": "^6.4.1", "react-native-svg": "^6.5.2",
"react-native-svg-image": "^2.0.1", "react-native-svg-image": "^2.0.1",
"react-native-vector-icons": "^4.6.0", "react-native-vector-icons": "^5.0.0",
"react-native-video": "^3.0.0", "react-native-video": "^3.2.0",
"react-native-video-controls": "^2.2.3", "react-native-video-controls": "^2.2.3",
"react-native-zeroconf": "^0.9.0", "react-native-zeroconf": "^0.9.0",
"react-redux": "^5.0.6", "react-redux": "^5.0.6",
"realm": "^2.13.0", "realm": "^2.14.2",
"redux": "^4.0.0", "redux": "^4.0.0",
"redux-enhancer-react-native-appstate": "^0.3.1", "redux-enhancer-react-native-appstate": "^0.3.1",
"redux-immutable-state-invariant": "^2.1.0", "redux-immutable-state-invariant": "^2.1.0",
"redux-saga": "^0.16.0", "redux-saga": "^0.16.0",
"regenerator-runtime": "^0.12.0", "regenerator-runtime": "^0.12.1",
"rn-fetch-blob": "^0.10.11", "rn-fetch-blob": "^0.10.12",
"snyk": "^1.88.1", "snyk": "^1.90.0",
"strip-ansi": "^4.0.0" "strip-ansi": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.0.0-beta.47", "@babel/core": "7.0.0-beta.47",
"@babel/plugin-proposal-decorators": "7.0.0-beta.47", "@babel/plugin-proposal-decorators": "7.0.0-beta.47",
"@storybook/addon-storyshots": "^3.4.8", "@storybook/addon-storyshots": "^3.4.10",
"babel-core": "^7.0.0-beta.47", "babel-core": "^7.0.0-beta.47",
"babel-eslint": "^8.2.6", "babel-eslint": "^8.2.6",
"babel-jest": "^23.4.0", "babel-jest": "^23.4.2",
"babel-plugin-transform-remove-console": "^6.9.4", "babel-plugin-transform-remove-console": "^6.9.4",
"babel-preset-react-native": "^5.0.2", "babel-preset-react-native": "^5.0.2",
"codecov": "^3.0.2", "codecov": "^3.0.4",
"detox": "^8.0.0", "detox": "^8.1.4",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.13.0", "eslint-plugin-import": "^2.13.0",
@ -95,12 +96,12 @@
"eslint-plugin-react": "^7.10.0", "eslint-plugin-react": "^7.10.0",
"eslint-plugin-react-native": "^3.2.0", "eslint-plugin-react-native": "^3.2.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^23.4.1", "jest": "^23.4.2",
"jest-cli": "^23.4.1", "jest-cli": "^23.4.2",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"react-dom": "^16.4.1", "react-dom": "^16.4.2",
"react-native-bundle-visualizer": "^1.3.0", "react-native-bundle-visualizer": "^1.3.0",
"react-test-renderer": "^16.4.1", "react-test-renderer": "^16.4.2",
"reactotron-react-native": "^2.0.0", "reactotron-react-native": "^2.0.0",
"reactotron-redux": "^2.0.0", "reactotron-redux": "^2.0.0",
"reactotron-redux-saga": "^2.0.0" "reactotron-redux-saga": "^2.0.0"
@ -128,7 +129,13 @@
"configurations": { "configurations": {
"ios.sim.debug": { "ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/RocketChatRN.app", "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/RocketChatRN.app",
"build": "xcodebuild -project ios/RocketChatRN.xcodeproj -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", "build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 7"
},
"ios.sim.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/RocketChatRN.app",
"build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator", "type": "ios.simulator",
"name": "iPhone 7" "name": "iPhone 7"
} }