[IMPROVE] Onboarding changes (#3387)

- Change the first screen of the app
- Minor changes on NewServerView and make it the first screen of the app
- Add "Create workspace" to ServerDropdown

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Reinaldo Neto 2021-09-23 11:17:53 -03:00 committed by GitHub
parent 30aab71db1
commit d2544a79c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 187 additions and 279 deletions

View File

@ -1444,6 +1444,7 @@ Array [
Object {
"fontSize": 12,
},
undefined,
]
}
>
@ -1605,6 +1606,7 @@ Array [
Object {
"fontSize": 12,
},
undefined,
]
}
>
@ -41153,6 +41155,7 @@ exports[`Storyshots Message Show a button as attachment 1`] = `
"color": "#ffffff",
},
undefined,
undefined,
]
}
>

View File

@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_NEW_SERVER, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app';
// Stacks
import AuthLoadingView from './views/AuthLoadingView';
// SetUsername Stack
@ -56,9 +56,7 @@ const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
<>
{root === ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null}
{root === ROOT_OUTSIDE || root === ROOT_NEW_SERVER ? (
<Stack.Screen name='OutsideStack' component={OutsideStack} />
) : null}
{root === ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null}
{root === ROOT_INSIDE && isMasterDetail ? (
<Stack.Screen name='MasterDetailStack' component={MasterDetailStack} />
) : null}

View File

@ -3,7 +3,6 @@ import { APP } from './actionsTypes';
export const ROOT_OUTSIDE = 'outside';
export const ROOT_INSIDE = 'inside';
export const ROOT_LOADING = 'loading';
export const ROOT_NEW_SERVER = 'newServer';
export const ROOT_SET_USERNAME = 'setUsername';
export function appStart({ root, ...args }) {

View File

@ -17,6 +17,7 @@ interface IButtonProps {
color: string;
fontSize: any;
style: any;
styleText?: any;
testID: string;
}
@ -48,7 +49,8 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
};
render() {
const { title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, ...otherProps } = this.props;
const { title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, styleText, ...otherProps } =
this.props;
const isPrimary = type === 'primary';
let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText;
@ -72,7 +74,7 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
{loading ? (
<ActivityIndicator color={textColor} />
) : (
<Text style={[styles.text, { color: textColor }, fontSize && { fontSize }]} accessibilityLabel={title}>
<Text style={[styles.text, { color: textColor }, fontSize && { fontSize }, styleText]} accessibilityLabel={title}>
{title}
</Text>
)}

View File

@ -7,7 +7,6 @@ const initialState = {
server: '',
version: null,
loading: true,
adding: false,
previousServer: null,
changingServer: false
};
@ -58,13 +57,11 @@ export default function server(state = initialState, action) {
case SERVER.INIT_ADD:
return {
...state,
adding: true,
previousServer: action.previousServer
};
case SERVER.FINISH_ADD:
return {
...state,
adding: false,
previousServer: null
};
default:

View File

@ -8,7 +8,7 @@ import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'
import database from '../lib/database';
import RocketChat from '../lib/rocketchat';
import EventEmitter from '../utils/events';
import { ROOT_INSIDE, ROOT_NEW_SERVER, appInit, appStart } from '../actions/app';
import { ROOT_INSIDE, ROOT_OUTSIDE, appInit, appStart } from '../actions/app';
import { localAuthenticate } from '../utils/localAuthentication';
import { goRoom } from '../utils/goRoom';
import { loginRequest } from '../actions/login';
@ -180,7 +180,7 @@ const handleOpen = function* handleOpen({ params }) {
yield fallbackNavigation();
return;
}
yield put(appStart({ root: ROOT_NEW_SERVER }));
yield put(appStart({ root: ROOT_OUTSIDE }));
yield put(serverInitAdd(server));
yield delay(1000);
EventEmitter.emit('NewServer', { server: host });

View File

@ -118,8 +118,6 @@ const fetchRooms = function* fetchRooms() {
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
try {
const adding = yield select(state => state.server.adding);
RocketChat.getUserPresence(user.id);
const server = yield select(getServer);
@ -170,24 +168,10 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield put(setUser(user));
EventEmitter.emit('connected');
let currentRoot;
if (adding) {
yield put(serverFinishAdd());
yield put(appStart({ root: ROOT_INSIDE }));
} else {
currentRoot = yield select(state => state.app.root);
if (currentRoot !== ROOT_INSIDE) {
yield put(appStart({ root: ROOT_INSIDE }));
}
}
// after a successful login, check if it's been invited via invite link
currentRoot = yield select(state => state.app.root);
if (currentRoot === ROOT_INSIDE) {
const inviteLinkToken = yield select(state => state.inviteLinks.token);
if (inviteLinkToken) {
yield put(inviteLinksRequest(inviteLinkToken));
}
yield put(appStart({ root: ROOT_INSIDE }));
const inviteLinkToken = yield select(state => state.inviteLinks.token);
if (inviteLinkToken) {
yield put(inviteLinksRequest(inviteLinkToken));
}
} catch (e) {
log(e);

View File

@ -1,13 +1,11 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { ThemeContext } from '../theme';
import { ModalAnimation, StackAnimation, defaultHeader, themedHeader } from '../utils/navigation';
// Outside Stack
import OnboardingView from '../views/OnboardingView';
import NewServerView from '../views/NewServerView';
import WorkspaceView from '../views/WorkspaceView';
import LoginView from '../views/LoginView';
@ -15,18 +13,14 @@ import ForgotPasswordView from '../views/ForgotPasswordView';
import RegisterView from '../views/RegisterView';
import LegalView from '../views/LegalView';
import AuthenticationWebView from '../views/AuthenticationWebView';
import { ROOT_OUTSIDE } from '../actions/app';
// Outside
const Outside = createStackNavigator();
const _OutsideStack = ({ root }) => {
const _OutsideStack = () => {
const { theme } = React.useContext(ThemeContext);
return (
<Outside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
{root === ROOT_OUTSIDE ? (
<Outside.Screen name='OnboardingView' component={OnboardingView} options={OnboardingView.navigationOptions} />
) : null}
<Outside.Screen name='NewServerView' component={NewServerView} options={NewServerView.navigationOptions} />
<Outside.Screen name='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} />
<Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} />
@ -41,10 +35,6 @@ const mapStateToProps = state => ({
root: state.app.root
});
_OutsideStack.propTypes = {
root: PropTypes.string
};
const OutsideStack = connect(mapStateToProps)(_OutsideStack);
// OutsideStackModal

View File

@ -1,9 +1,4 @@
export default {
// ONBOARDING VIEW
ONBOARD_JOIN_A_WORKSPACE: 'onboard_join_a_workspace',
ONBOARD_CREATE_NEW_WORKSPACE: 'onboard_create_new_workspace',
ONBOARD_CREATE_NEW_WORKSPACE_F: 'onboard_create_new_workspace_f',
// NEW SERVER VIEW
NS_CONNECT_TO_WORKSPACE: 'ns_connect_to_workspace',
NS_JOIN_OPEN_WORKSPACE: 'ns_join_open_workspace',
@ -78,6 +73,7 @@ export default {
RL_GROUP_CHANNELS_BY_TYPE: 'rl_group_channels_by_type',
RL_GROUP_CHANNELS_BY_FAVORITE: 'rl_group_channels_by_favorite',
RL_GROUP_CHANNELS_BY_UNREAD: 'rl_group_channels_by_unread',
RL_CREATE_NEW_WORKSPACE: 'rl_create_new_workspace',
// QUEUE LIST VIEW
QL_GO_ROOM: 'ql_go_room',

View File

@ -1,14 +1,11 @@
import { Dimensions } from 'react-native';
import { isTablet } from './deviceInfo';
const { width, height } = Dimensions.get('window');
const guidelineBaseWidth = isTablet ? 600 : 375;
const guidelineBaseHeight = isTablet ? 800 : 667;
const scale = size => (width / guidelineBaseWidth) * size;
const verticalScale = size => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5) => size + (scale(size) - size) * factor;
// TODO: we need to refactor this
const scale = (size, width) => (width / guidelineBaseWidth) * size;
const verticalScale = (size, height) => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5, width) => size + (scale(size, width) - size) * factor;
export { scale, verticalScale, moderateScale };

View File

@ -10,9 +10,7 @@ import Item from './Item';
const styles = StyleSheet.create({
container: {
zIndex: 1,
marginTop: 24,
marginBottom: 32
zIndex: 1
},
inputContainer: {
marginTop: 0,

View File

@ -1,15 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { BackHandler, Keyboard, StyleSheet, Text, View } from 'react-native';
import { Text, Keyboard, StyleSheet, View, BackHandler, Image } from 'react-native';
import { connect } from 'react-redux';
import { Base64 } from 'js-base64';
import parse from 'url-parse';
import { Q } from '@nozbe/watermelondb';
import { TouchableOpacity } from 'react-native-gesture-handler';
import Orientation from 'react-native-orientation-locker';
import UserPreferences from '../../lib/userPreferences';
import EventEmitter from '../../utils/events';
import { selectServerRequest, serverRequest } from '../../actions/server';
import { selectServerRequest, serverRequest, serverFinishAdd as serverFinishAddAction } from '../../actions/server';
import { inviteLinksClear as inviteLinksClearAction } from '../../actions/inviteLinks';
import sharedStyles from '../Styles';
import Button from '../../containers/Button';
@ -27,31 +28,38 @@ import database from '../../lib/database';
import { sanitizeLikeString } from '../../lib/database/utils';
import SSLPinning from '../../utils/sslPinning';
import RocketChat from '../../lib/rocketchat';
import { isTablet } from '../../utils/deviceInfo';
import { verticalScale, moderateScale } from '../../utils/scaling';
import { withDimensions } from '../../dimensions';
import ServerInput from './ServerInput';
const styles = StyleSheet.create({
onboardingImage: {
alignSelf: 'center',
resizeMode: 'contain'
},
title: {
...sharedStyles.textBold,
fontSize: 22
letterSpacing: 0,
alignSelf: 'center'
},
subtitle: {
...sharedStyles.textRegular,
alignSelf: 'center'
},
certificatePicker: {
marginBottom: 32,
alignItems: 'center',
justifyContent: 'flex-end'
},
chooseCertificateTitle: {
fontSize: 13,
...sharedStyles.textRegular
},
chooseCertificate: {
fontSize: 13,
...sharedStyles.textSemibold
},
description: {
...sharedStyles.textRegular,
fontSize: 14,
textAlign: 'left',
marginBottom: 24
textAlign: 'center'
},
connectButton: {
marginBottom: 0
@ -59,23 +67,22 @@ const styles = StyleSheet.create({
});
class NewServerView extends React.Component {
static navigationOptions = () => ({
title: I18n.t('Workspaces')
});
static propTypes = {
navigation: PropTypes.object,
theme: PropTypes.string,
connecting: PropTypes.bool.isRequired,
connectServer: PropTypes.func.isRequired,
selectServer: PropTypes.func.isRequired,
adding: PropTypes.bool,
previousServer: PropTypes.string,
inviteLinksClear: PropTypes.func
inviteLinksClear: PropTypes.func,
serverFinishAdd: PropTypes.func
};
constructor(props) {
super(props);
if (!isTablet) {
Orientation.lockToPortrait();
}
this.setHeader();
this.state = {
@ -92,25 +99,27 @@ class NewServerView extends React.Component {
this.queryServerHistory();
}
componentDidUpdate(prevProps) {
const { adding } = this.props;
if (prevProps.adding !== adding) {
this.setHeader();
}
}
componentWillUnmount() {
EventEmitter.removeListener('NewServer', this.handleNewServerEvent);
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
const { previousServer, serverFinishAdd } = this.props;
if (previousServer) {
serverFinishAdd();
}
}
setHeader = () => {
const { adding, navigation } = this.props;
if (adding) {
navigation.setOptions({
const { previousServer, navigation } = this.props;
if (previousServer) {
return navigation.setOptions({
headerTitle: I18n.t('Workspaces'),
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} onPress={this.close} testID='new-server-view-close' />
});
}
return navigation.setOptions({
headerShown: false
});
};
handleBackPress = () => {
@ -273,16 +282,26 @@ class NewServerView extends React.Component {
renderCertificatePicker = () => {
const { certificate } = this.state;
const { theme } = this.props;
const { theme, width, height, previousServer } = this.props;
return (
<View style={styles.certificatePicker}>
<Text style={[styles.chooseCertificateTitle, { color: themes[theme].auxiliaryText }]}>
<View
style={[
styles.certificatePicker,
{
marginBottom: verticalScale(previousServer && !isTablet ? 10 : 30, height)
}
]}>
<Text
style={[
styles.chooseCertificateTitle,
{ color: themes[theme].auxiliaryText, fontSize: moderateScale(13, null, width) }
]}>
{certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')}
</Text>
<TouchableOpacity
onPress={certificate ? this.handleRemove : this.chooseCertificate}
testID='new-server-choose-certificate'>
<Text style={[styles.chooseCertificate, { color: themes[theme].tintColor }]}>
<Text style={[styles.chooseCertificate, { color: themes[theme].tintColor, fontSize: moderateScale(13, null, width) }]}>
{certificate ?? I18n.t('Apply_Your_Certificate')}
</Text>
</TouchableOpacity>
@ -291,12 +310,48 @@ class NewServerView extends React.Component {
};
render() {
const { connecting, theme } = this.props;
const { connecting, theme, previousServer, width, height } = this.props;
const { text, connectingOpen, serversHistory } = this.state;
const marginTop = previousServer ? 0 : 35;
return (
<FormContainer theme={theme} testID='new-server-view' keyboardShouldPersistTaps='never'>
<FormContainerInner>
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Join_your_workspace')}</Text>
<Image
style={[
styles.onboardingImage,
{
marginBottom: verticalScale(10, height),
marginTop: isTablet ? 0 : verticalScale(marginTop, height),
width: verticalScale(100, height),
height: verticalScale(100, height)
}
]}
source={require('../../static/images/logo.png')}
fadeDuration={0}
/>
<Text
style={[
styles.title,
{
color: themes[theme].titleText,
fontSize: moderateScale(22, null, width),
marginBottom: verticalScale(8, height)
}
]}>
Rocket.Chat
</Text>
<Text
style={[
styles.subtitle,
{
color: themes[theme].controlText,
fontSize: moderateScale(16, null, width),
marginBottom: verticalScale(30, height)
}
]}>
{I18n.t('Onboarding_subtitle')}
</Text>
<ServerInput
text={text}
theme={theme}
@ -312,12 +367,20 @@ class NewServerView extends React.Component {
onPress={this.submit}
disabled={!text || connecting}
loading={!connectingOpen && connecting}
style={styles.connectButton}
style={[styles.connectButton, { marginTop: verticalScale(16, height) }]}
theme={theme}
testID='new-server-view-button'
/>
<OrSeparator theme={theme} />
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>
<Text
style={[
styles.description,
{
color: themes[theme].auxiliaryText,
fontSize: moderateScale(14, null, width),
marginBottom: verticalScale(16, height)
}
]}>
{I18n.t('Onboarding_join_open_description')}
</Text>
<Button
@ -339,14 +402,14 @@ class NewServerView extends React.Component {
const mapStateToProps = state => ({
connecting: state.server.connecting,
adding: state.server.adding,
previousServer: state.server.previousServer
});
const mapDispatchToProps = dispatch => ({
connectServer: (...params) => dispatch(serverRequest(...params)),
selectServer: server => dispatch(selectServerRequest(server)),
inviteLinksClear: () => dispatch(inviteLinksClearAction())
inviteLinksClear: () => dispatch(inviteLinksClearAction()),
serverFinishAdd: () => dispatch(serverFinishAddAction())
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewServerView));
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(NewServerView)));

View File

@ -1,87 +0,0 @@
import React from 'react';
import { Image, Linking, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import Orientation from 'react-native-orientation-locker';
import I18n from '../../i18n';
import Button from '../../containers/Button';
import { isTablet } from '../../utils/deviceInfo';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
import { events, logEvent } from '../../utils/log';
import styles from './styles';
class OnboardingView extends React.Component {
static navigationOptions = {
headerShown: false
};
static propTypes = {
navigation: PropTypes.object,
theme: PropTypes.string
};
constructor(props) {
super(props);
if (!isTablet) {
Orientation.lockToPortrait();
}
}
shouldComponentUpdate(nextProps) {
const { theme } = this.props;
if (theme !== nextProps.theme) {
return true;
}
return false;
}
connectServer = () => {
logEvent(events.ONBOARD_JOIN_A_WORKSPACE);
const { navigation } = this.props;
navigation.navigate('NewServerView');
};
createWorkspace = async () => {
logEvent(events.ONBOARD_CREATE_NEW_WORKSPACE);
try {
await Linking.openURL('https://cloud.rocket.chat/trial');
} catch {
logEvent(events.ONBOARD_CREATE_NEW_WORKSPACE_F);
}
};
render() {
const { theme } = this.props;
return (
<FormContainer theme={theme} testID='onboarding-view'>
<FormContainerInner>
<Image style={styles.onboarding} source={require('../../static/images/logo.png')} fadeDuration={0} />
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Onboarding_title')}</Text>
<Text style={[styles.subtitle, { color: themes[theme].controlText }]}>{I18n.t('Onboarding_subtitle')}</Text>
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_description')}</Text>
<View style={styles.buttonsContainer}>
<Button
title={I18n.t('Onboarding_join_workspace')}
type='primary'
onPress={this.connectServer}
theme={theme}
testID='join-workspace'
/>
<Button
title={I18n.t('Create_a_new_workspace')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.createWorkspace}
theme={theme}
testID='create-workspace-button'
/>
</View>
</FormContainerInner>
</FormContainer>
);
}
}
export default withTheme(OnboardingView);

View File

@ -1,41 +0,0 @@
import { StyleSheet } from 'react-native';
import { moderateScale, verticalScale } from '../../utils/scaling';
import { isTablet } from '../../utils/deviceInfo';
import sharedStyles from '../Styles';
export default StyleSheet.create({
onboarding: {
alignSelf: 'center',
marginTop: isTablet ? 0 : verticalScale(116),
marginBottom: verticalScale(50),
maxHeight: verticalScale(150),
resizeMode: 'contain',
width: 100,
height: 100
},
title: {
...sharedStyles.textBold,
letterSpacing: 0,
fontSize: moderateScale(24),
alignSelf: 'center',
marginBottom: verticalScale(8)
},
subtitle: {
...sharedStyles.textRegular,
fontSize: moderateScale(16),
alignSelf: 'center',
marginBottom: verticalScale(24)
},
description: {
...sharedStyles.textRegular,
...sharedStyles.textAlignCenter,
fontSize: moderateScale(14),
alignSelf: 'center',
marginHorizontal: 20
},
buttonsContainer: {
marginBottom: verticalScale(10),
marginTop: verticalScale(30)
}
});

View File

@ -1,13 +1,14 @@
import React, { Component } from 'react';
import { Animated, Easing, FlatList, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native';
import PropTypes from 'prop-types';
import { batch, connect } from 'react-redux';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import * as List from '../../containers/List';
import Button from '../../containers/Button';
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
import { selectServerRequest as selectServerRequestAction, serverInitAdd as serverInitAddAction } from '../../actions/server';
import { ROOT_NEW_SERVER, appStart as appStartAction } from '../../actions/app';
import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app';
import RocketChat from '../../lib/rocketchat';
import I18n from '../../i18n';
import EventEmitter from '../../utils/events';
@ -19,7 +20,7 @@ import { KEY_COMMAND, handleCommandSelectServer } from '../../commands';
import { isTablet } from '../../utils/deviceInfo';
import { localAuthenticate } from '../../utils/localAuthentication';
import { showConfirmationAlert } from '../../utils/info';
import { events, logEvent } from '../../utils/log';
import log, { events, logEvent } from '../../utils/log';
import { headerHeight } from '../../containers/Header';
import { goRoom } from '../../utils/goRoom';
import UserPreferences from '../../lib/userPreferences';
@ -97,10 +98,19 @@ class ServerDropdown extends Component {
}).start(() => toggleServerDropdown());
};
createWorkspace = async () => {
logEvent(events.RL_CREATE_NEW_WORKSPACE);
try {
await Linking.openURL('https://cloud.rocket.chat/trial');
} catch (e) {
log(e);
}
};
navToNewServer = previousServer => {
const { appStart, initAdd } = this.props;
batch(() => {
appStart({ root: ROOT_NEW_SERVER });
appStart({ root: ROOT_OUTSIDE });
initAdd(previousServer);
});
};
@ -181,7 +191,7 @@ class ServerDropdown extends Component {
const { servers } = this.state;
const { theme, isMasterDetail, insets } = this.props;
const maxRows = 4;
const initialTop = 41 + Math.min(servers.length, maxRows) * ROW_HEIGHT;
const initialTop = 87 + Math.min(servers.length, maxRows) * ROW_HEIGHT;
const statusBarHeight = insets?.top ?? 0;
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
const translateY = this.animatedValue.interpolate({
@ -230,6 +240,17 @@ class ServerDropdown extends Component {
ItemSeparatorComponent={List.Separator}
keyboardShouldPersistTaps='always'
/>
<List.Separator />
<Button
title={I18n.t('Create_a_new_workspace')}
type='secondary'
onPress={this.createWorkspace}
theme={theme}
testID='rooms-list-header-create-workspace-button'
style={styles.buttonCreateWorkspace}
color={themes[theme].tintColor}
styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]}
/>
</Animated.View>
</>
);

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { BackHandler, FlatList, Keyboard, RefreshControl, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { batch, connect } from 'react-redux';
import { dequal } from 'dequal';
import Orientation from 'react-native-orientation-locker';
import { Q } from '@nozbe/watermelondb';
@ -19,12 +19,13 @@ import {
roomsRequest as roomsRequestAction,
toggleSortDropdown as toggleSortDropdownAction
} from '../../actions/rooms';
import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app';
import debounce from '../../utils/debounce';
import { isIOS, isTablet } from '../../utils/deviceInfo';
import * as HeaderButton from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
import ActivityIndicator from '../../containers/ActivityIndicator';
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
import { selectServerRequest as selectServerRequestAction, serverInitAdd as serverInitAddAction } from '../../actions/server';
import { animateNextTransition } from '../../utils/layoutAnimation';
import { withTheme } from '../../theme';
import { themes } from '../../constants/colors';
@ -133,7 +134,8 @@ class RoomsListView extends React.Component {
insets: PropTypes.object,
queueSize: PropTypes.number,
inquiryEnabled: PropTypes.bool,
encryptionBanner: PropTypes.string
encryptionBanner: PropTypes.string,
initAdd: PropTypes.func
};
constructor(props) {
@ -763,7 +765,7 @@ class RoomsListView extends React.Component {
};
handleCommands = ({ event }) => {
const { navigation, server, isMasterDetail } = this.props;
const { navigation, server, isMasterDetail, appStart, initAdd } = this.props;
const { input } = event;
if (handleCommandShowPreferences(event)) {
navigation.navigate('SettingsView');
@ -782,7 +784,10 @@ class RoomsListView extends React.Component {
navigation.navigate('NewMessageStack');
}
} else if (handleCommandAddNewServer(event)) {
navigation.navigate('NewServerView', { previousServer: server });
batch(() => {
appStart({ root: ROOT_OUTSIDE });
initAdd(server);
});
}
};
@ -967,7 +972,9 @@ const mapDispatchToProps = dispatch => ({
closeSearchHeader: () => dispatch(closeSearchHeaderAction()),
roomsRequest: params => dispatch(roomsRequestAction(params)),
selectServerRequest: server => dispatch(selectServerRequestAction(server)),
closeServerDropdown: () => dispatch(closeServerDropdownAction())
closeServerDropdown: () => dispatch(closeServerDropdownAction()),
appStart: params => dispatch(appStartAction(params)),
initAdd: previousServer => dispatch(serverInitAddAction(previousServer))
});
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView))));

View File

@ -56,5 +56,10 @@ export default StyleSheet.create({
marginRight: 12,
paddingVertical: 10,
...sharedStyles.textRegular
},
buttonCreateWorkspace: {
height: 46,
justifyContent: 'center',
marginBottom: 0
}
});

View File

@ -96,7 +96,6 @@ class WorkspaceView extends React.Component {
const mapStateToProps = state => ({
server: state.server.server,
adding: state.server.adding,
Site_Name: state.settings.Site_Name,
Site_Url: state.settings.Site_Url,
Assets_favicon_512: state.settings.Assets_favicon_512,

View File

@ -1,10 +1,6 @@
const data = require('../data');
async function navigateToWorkspace(server = data.server) {
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(10000);
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(60000);
@ -16,9 +12,6 @@ async function navigateToWorkspace(server = data.server) {
}
async function navigateToLogin(server) {
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(20000);
await navigateToWorkspace(server);
await element(by.id('workspace-view-login')).tap();
await waitFor(element(by.id('login-view')))
@ -28,9 +21,6 @@ async function navigateToLogin(server) {
}
async function navigateToRegister(server) {
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(20000);
await navigateToWorkspace(server);
await element(by.id('workspace-view-register')).tap();
await waitFor(element(by.id('register-view')))
@ -70,10 +60,10 @@ async function logout() {
.withTimeout(10000);
await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist();
await element(by.text('Logout')).tap();
await waitFor(element(by.id('onboarding-view')))
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(10000);
await expect(element(by.id('onboarding-view'))).toBeVisible();
await expect(element(by.id('new-server-view'))).toBeVisible();
}
async function mockMessage(message, isThread = false) {

View File

@ -5,7 +5,7 @@ const reopenAndCheckServer = async server => {
await device.launchApp({ permissions: { notifications: 'YES' } });
await waitFor(element(by.id('rooms-list-view')))
.toBeVisible()
.withTimeout(6000);
.withTimeout(10000);
await checkServer(server);
};
@ -19,13 +19,21 @@ describe('Change server', () => {
.withTimeout(10000);
});
it('should login to server, add new server, close the app, open the app and show previous logged server', async () => {
it('should open the dropdown button, have the server add button and create workspace button', async () => {
await element(by.id('rooms-list-header-server-dropdown-button')).tap();
await waitFor(element(by.id('rooms-list-header-server-dropdown')))
.toBeVisible()
.withTimeout(5000);
await element(by.id('rooms-list-header-server-add')).tap();
await waitFor(element(by.id('rooms-list-header-server-add')))
.toBeVisible()
.withTimeout(5000);
await waitFor(element(by.id('rooms-list-header-create-workspace-button')))
.toBeVisible()
.withTimeout(5000);
});
it('should login to server, add new server, close the app, open the app and show previous logged server', async () => {
await element(by.id('rooms-list-header-server-add')).tap();
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(6000);

View File

@ -37,11 +37,10 @@ describe('i18n', () => {
},
delete: true
});
await waitFor(element(by.id('onboarding-view')))
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(20000);
await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible();
await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible();
await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
});
it("OS set to unavailable language and fallback to 'en'", async () => {
@ -52,11 +51,10 @@ describe('i18n', () => {
locale: 'es-MX'
}
});
await waitFor(element(by.id('onboarding-view')))
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(20000);
await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible();
await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible();
await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
});
/**
@ -71,9 +69,6 @@ describe('i18n', () => {
// locale: "nl"
// }
// });
// await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000);
// await expect(element(by.id('join-workspace').and(by.label('Word lid van een werkruimte')))).toBeVisible();
// await expect(element(by.id('create-workspace-button').and(by.label('Een nieuwe werkruimte aanmaken')))).toBeVisible();
// });
});

View File

@ -3,22 +3,18 @@ const data = require('../../data');
describe('Onboarding', () => {
before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await waitFor(element(by.id('onboarding-view')))
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(20000);
});
describe('Render', () => {
it('should have onboarding screen', async () => {
await expect(element(by.id('onboarding-view'))).toBeVisible();
await expect(element(by.id('new-server-view'))).toBeVisible();
});
it('should have "Join a workspace"', async () => {
await expect(element(by.id('join-workspace'))).toBeVisible();
});
it('should have "Create a new workspace"', async () => {
await expect(element(by.id('create-workspace-button'))).toBeVisible();
it('should have "Join our open workspace"', async () => {
await expect(element(by.id('new-server-view-open'))).toBeVisible();
});
});
@ -27,13 +23,6 @@ describe('Onboarding', () => {
// // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
// });
it('should navigate to join a workspace', async () => {
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(60000);
});
it('should enter an invalid server and get error', async () => {
await element(by.id('new-server-view-input')).typeText('invalidtest\n');
const errorText = 'Oops!';
@ -52,13 +41,9 @@ describe('Onboarding', () => {
it('should enter a valid server without login services and navigate to login', async () => {
await device.launchApp({ newInstance: true });
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(60000);
.withTimeout(5000);
await element(by.id('new-server-view-input')).typeText(`${data.server}\n`);
await waitFor(element(by.id('workspace-view')))
.toBeVisible()

View File

@ -11,7 +11,6 @@ describe('Server history', () => {
await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password);
await logout();
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(60000);