From f3ddf60a57248d17364d879b8fbbf166e3faa665 Mon Sep 17 00:00:00 2001
From: Diego Mello <diegolmello@gmail.com>
Date: Wed, 27 Feb 2019 11:26:40 -0300
Subject: [PATCH] Remove drawer (#653)

* Remove drawer (layout needs to be changed in future releases, though)
* Don't navigate outside on logout if there's other logged server
* Update react-native-navigation
---
 app/index.js                              |  23 +--
 app/sagas/login.js                        |  21 ++-
 app/views/ProfileView/index.js            |  41 +-----
 app/views/RoomsListView/ServerDropdown.js |  14 +-
 app/views/RoomsListView/index.js          |  17 ++-
 app/views/SettingsView/index.js           |  41 +-----
 app/views/SidebarView.js                  | 171 +++++++++++-----------
 e2e/08-room.spec.js                       |   4 +-
 8 files changed, 144 insertions(+), 188 deletions(-)

diff --git a/app/index.js b/app/index.js
index 289e8fbd9..e916e59d2 100644
--- a/app/index.js
+++ b/app/index.js
@@ -18,26 +18,17 @@ const startLogged = () => {
 	Navigation.loadView('RoomHeaderView');
 	Navigation.loadView('SettingsView');
 	Navigation.loadView('SidebarView');
+
 	Navigation.setRoot({
 		root: {
-			sideMenu: {
-				left: {
+			stack: {
+				id: 'AppRoot',
+				children: [{
 					component: {
-						id: 'SidebarView',
-						name: 'SidebarView'
+						id: 'RoomsListView',
+						name: 'RoomsListView'
 					}
-				},
-				center: {
-					stack: {
-						id: 'AppRoot',
-						children: [{
-							component: {
-								id: 'RoomsListView',
-								name: 'RoomsListView'
-							}
-						}]
-					}
-				}
+				}]
 			}
 		}
 	});
diff --git a/app/sagas/login.js b/app/sagas/login.js
index 5ccfeeb5d..78a17d9e6 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -6,11 +6,12 @@ import {
 import Navigation from '../lib/Navigation';
 import * as types from '../actions/actionsTypes';
 import { appStart } from '../actions';
-import { serverFinishAdd } from '../actions/server';
+import { serverFinishAdd, selectServerRequest } from '../actions/server';
 import { loginFailure, loginSuccess } from '../actions/login';
 import RocketChat from '../lib/rocketchat';
 import log from '../utils/log';
 import I18n from '../i18n';
+import database from '../lib/realm';
 
 const getServer = state => state.server.server;
 const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
@@ -60,8 +61,26 @@ const handleLogout = function* handleLogout() {
 	if (server) {
 		try {
 			yield call(logoutCall, { server });
+			const { serversDB } = database.databases;
+			// all servers
+			const servers = yield serversDB.objects('servers');
+			// filter logging out server and delete it
+			const serverRecord = servers.filtered('id = $0', server);
+			serversDB.write(() => {
+				serversDB.delete(serverRecord);
+			});
+			// see if there's other logged in servers and selects first one
+			if (servers.length > 0) {
+				const newServer = servers[0].id;
+				const token = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ newServer }`);
+				if (token) {
+					return yield put(selectServerRequest(newServer));
+				}
+			}
+			// if there's no servers, go outside
 			yield put(appStart('outside'));
 		} catch (e) {
+			yield put(appStart('outside'));
 			log('handleLogout', e);
 		}
 	}
diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js
index 27f00ea84..c9199d9de 100644
--- a/app/views/ProfileView/index.js
+++ b/app/views/ProfileView/index.js
@@ -1,8 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import {
-	View, ScrollView, Keyboard, BackHandler
-} from 'react-native';
+import { View, ScrollView, Keyboard } from 'react-native';
 import { connect } from 'react-redux';
 import Dialog from 'react-native-dialog';
 import SHA256 from 'js-sha256';
@@ -12,7 +10,6 @@ import RNPickerSelect from 'react-native-picker-select';
 import SafeAreaView from 'react-native-safe-area-view';
 import equal from 'deep-equal';
 
-import Navigation from '../../lib/Navigation';
 import LoggedView from '../View';
 import KeyboardView from '../../presentation/KeyboardView';
 import sharedStyles from '../Styles';
@@ -26,9 +23,7 @@ import I18n from '../../i18n';
 import Button from '../../containers/Button';
 import Avatar from '../../containers/Avatar';
 import Touch from '../../utils/touch';
-import { appStart as appStartAction } from '../../actions';
 import { setUser as setUserAction } from '../../actions/login';
-import Icons from '../../lib/Icons';
 
 @connect(state => ({
 	user: {
@@ -42,7 +37,6 @@ import Icons from '../../lib/Icons';
 	Accounts_CustomFields: state.settings.Accounts_CustomFields,
 	baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
 }), dispatch => ({
-	appStart: () => dispatch(appStartAction()),
 	setUser: params => dispatch(setUserAction(params))
 }))
 /** @extends React.Component */
@@ -50,32 +44,17 @@ export default class ProfileView extends LoggedView {
 	static options() {
 		return {
 			topBar: {
-				leftButtons: [{
-					id: 'settings',
-					icon: Icons.getSource('settings'),
-					testID: 'rooms-list-view-sidebar'
-				}],
 				title: {
 					text: I18n.t('Profile')
 				}
-			},
-			sideMenu: {
-				left: {
-					enabled: true
-				},
-				right: {
-					enabled: true
-				}
 			}
 		};
 	}
 
 	static propTypes = {
 		baseUrl: PropTypes.string,
-		componentId: PropTypes.string,
 		user: PropTypes.object,
 		Accounts_CustomFields: PropTypes.string,
-		appStart: PropTypes.func,
 		setUser: PropTypes.func
 	}
 
@@ -94,8 +73,6 @@ export default class ProfileView extends LoggedView {
 			avatarSuggestions: {},
 			customFields: {}
 		};
-		Navigation.events().bindComponent(this);
-		BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
 	}
 
 	async componentDidMount() {
@@ -126,22 +103,6 @@ export default class ProfileView extends LoggedView {
 		return false;
 	}
 
-	componentWillUnmount() {
-		BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
-	}
-
-	navigationButtonPressed = ({ buttonId }) => {
-		if (buttonId === 'settings') {
-			Navigation.toggleDrawer();
-		}
-	}
-
-	handleBackPress = () => {
-		const { appStart } = this.props;
-		appStart('background');
-		return false;
-	}
-
 	setAvatar = (avatar) => {
 		this.setState({ avatar });
 	}
diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js
index 3f4508819..34db1f007 100644
--- a/app/views/RoomsListView/ServerDropdown.js
+++ b/app/views/RoomsListView/ServerDropdown.js
@@ -81,6 +81,13 @@ export default class ServerDropdown extends Component {
 		}
 	}
 
+	componentWillUnmount() {
+		if (this.newServerTimeout) {
+			clearTimeout(this.newServerTimeout);
+			this.newServerTimeout = false;
+		}
+	}
+
 	updateState = () => {
 		const { servers } = this;
 		this.setState({ servers });
@@ -137,12 +144,7 @@ export default class ServerDropdown extends Component {
 			const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
 			if (!token) {
 				appStart();
-				try {
-					this.sdk.disconnect();
-				} catch (error) {
-					console.warn(error);
-				}
-				setTimeout(() => {
+				this.newServerTimeout = setTimeout(() => {
 					EventEmitter.emit('NewServer', { server });
 				}, 1000);
 			} else {
diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js
index 8d78aa8b4..182344753 100644
--- a/app/views/RoomsListView/index.js
+++ b/app/views/RoomsListView/index.js
@@ -270,7 +270,22 @@ export default class RoomsListView extends LoggedView {
 				}
 			});
 		} else if (buttonId === 'settings') {
-			Navigation.toggleDrawer();
+			Navigation.showModal({
+				stack: {
+					children: [{
+						component: {
+							name: 'SidebarView',
+							options: {
+								topBar: {
+									title: {
+										text: I18n.t('Settings')
+									}
+								}
+							}
+						}
+					}]
+				}
+			});
 		} else if (buttonId === 'search') {
 			this.initSearchingAndroid();
 		} else if (buttonId === 'back') {
diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js
index 6ca848e70..a53ae7ab5 100644
--- a/app/views/SettingsView/index.js
+++ b/app/views/SettingsView/index.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { View, ScrollView, BackHandler } from 'react-native';
+import { View, ScrollView } from 'react-native';
 import RNPickerSelect from 'react-native-picker-select';
 import { connect } from 'react-redux';
 import SafeAreaView from 'react-native-safe-area-view';
@@ -18,36 +18,20 @@ import Loading from '../../containers/Loading';
 import { showErrorAlert, showToast } from '../../utils/info';
 import log from '../../utils/log';
 import { setUser as setUserAction } from '../../actions/login';
-import { appStart as appStartAction } from '../../actions';
-import Icons from '../../lib/Icons';
 
 @connect(state => ({
 	userLanguage: state.login.user && state.login.user.language
 }), dispatch => ({
-	setUser: params => dispatch(setUserAction(params)),
-	appStart: () => dispatch(appStartAction())
+	setUser: params => dispatch(setUserAction(params))
 }))
 /** @extends React.Component */
 export default class SettingsView extends LoggedView {
 	static options() {
 		return {
 			topBar: {
-				leftButtons: [{
-					id: 'settings',
-					icon: Icons.getSource('settings'),
-					testID: 'rooms-list-view-sidebar'
-				}],
 				title: {
 					text: I18n.t('Settings')
 				}
-			},
-			sideMenu: {
-				left: {
-					enabled: true
-				},
-				right: {
-					enabled: true
-				}
 			}
 		};
 	}
@@ -55,8 +39,7 @@ export default class SettingsView extends LoggedView {
 	static propTypes = {
 		componentId: PropTypes.string,
 		userLanguage: PropTypes.string,
-		setUser: PropTypes.func,
-		appStart: PropTypes.func
+		setUser: PropTypes.func
 	}
 
 	constructor(props) {
@@ -85,8 +68,6 @@ export default class SettingsView extends LoggedView {
 			}],
 			saving: false
 		};
-		Navigation.events().bindComponent(this);
-		BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
 	}
 
 	shouldComponentUpdate(nextProps, nextState) {
@@ -104,22 +85,6 @@ export default class SettingsView extends LoggedView {
 		return false;
 	}
 
-	componentWillUnmount() {
-		BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
-	}
-
-	navigationButtonPressed = ({ buttonId }) => {
-		if (buttonId === 'settings') {
-			Navigation.toggleDrawer();
-		}
-	}
-
-	handleBackPress = () => {
-		const { appStart } = this.props;
-		appStart('background');
-		return false;
-	}
-
 	getLabel = (language) => {
 		const { languages } = this.state;
 		const l = languages.find(i => i.value === language);
diff --git a/app/views/SidebarView.js b/app/views/SidebarView.js
index 0408c3e76..ed44ddf4c 100644
--- a/app/views/SidebarView.js
+++ b/app/views/SidebarView.js
@@ -1,14 +1,13 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import {
-	ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView
+	ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView, Image
 } from 'react-native';
 import { connect } from 'react-redux';
 import Icon from 'react-native-vector-icons/MaterialIcons';
 import equal from 'deep-equal';
 
 import Navigation from '../lib/Navigation';
-import { setStackRoot as setStackRootAction } from '../actions';
 import { logout as logoutAction } from '../actions/login';
 import Avatar from '../containers/Avatar';
 import Status from '../containers/status';
@@ -18,7 +17,8 @@ import RocketChat from '../lib/rocketchat';
 import log from '../utils/log';
 import I18n from '../i18n';
 import scrollPersistTaps from '../utils/scrollPersistTaps';
-import { getReadableVersion } from '../utils/deviceInfo';
+import { getReadableVersion, isIOS, isAndroid } from '../utils/deviceInfo';
+import Icons from '../lib/Icons';
 
 const styles = StyleSheet.create({
 	container: {
@@ -34,14 +34,14 @@ const styles = StyleSheet.create({
 		width: 30,
 		alignItems: 'center'
 	},
+	itemCenter: {
+		flex: 1
+	},
 	itemText: {
 		marginVertical: 16,
 		fontWeight: 'bold',
 		color: '#292E35'
 	},
-	itemSelected: {
-		backgroundColor: '#F7F8FA'
-	},
 	separator: {
 		borderBottomWidth: StyleSheet.hairlineWidth,
 		borderColor: '#ddd',
@@ -79,13 +79,22 @@ const styles = StyleSheet.create({
 		fontWeight: '600',
 		color: '#292E35',
 		fontSize: 13
+	},
+	disclosureContainer: {
+		marginLeft: 6,
+		marginRight: 9,
+		alignItems: 'center',
+		justifyContent: 'center'
+	},
+	disclosureIndicator: {
+		width: 20,
+		height: 20
 	}
 });
 const keyExtractor = item => item.id;
 
 @connect(state => ({
 	Site_Name: state.settings.Site_Name,
-	stackRoot: state.app.stackRoot,
 	user: {
 		id: state.login.user && state.login.user.id,
 		language: state.login.user && state.login.user.language,
@@ -95,18 +104,27 @@ const keyExtractor = item => item.id;
 	},
 	baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
 }), dispatch => ({
-	logout: () => dispatch(logoutAction()),
-	setStackRoot: stackRoot => dispatch(setStackRootAction(stackRoot))
+	logout: () => dispatch(logoutAction())
 }))
 export default class Sidebar extends Component {
+	static options() {
+		return {
+			topBar: {
+				leftButtons: [{
+					id: 'cancel',
+					icon: isAndroid ? Icons.getSource('close', false) : undefined,
+					systemItem: 'cancel'
+				}]
+			}
+		};
+	}
+
 	static propTypes = {
 		baseUrl: PropTypes.string,
 		componentId: PropTypes.string,
 		Site_Name: PropTypes.string.isRequired,
-		stackRoot: PropTypes.string.isRequired,
 		user: PropTypes.object,
-		logout: PropTypes.func.isRequired,
-		setStackRoot: PropTypes.func
+		logout: PropTypes.func.isRequired
 	}
 
 	constructor(props) {
@@ -131,18 +149,13 @@ export default class Sidebar extends Component {
 
 	shouldComponentUpdate(nextProps, nextState) {
 		const { status, showStatus } = this.state;
-		const {
-			Site_Name, stackRoot, user, baseUrl
-		} = this.props;
+		const { Site_Name, user, baseUrl } = this.props;
 		if (nextState.showStatus !== showStatus) {
 			return true;
 		}
 		if (nextProps.Site_Name !== Site_Name) {
 			return true;
 		}
-		if (nextProps.stackRoot !== stackRoot) {
-			return true;
-		}
 		if (nextProps.Site_Name !== Site_Name) {
 			return true;
 		}
@@ -166,11 +179,6 @@ export default class Sidebar extends Component {
 		return false;
 	}
 
-	handleChangeStack = (event) => {
-		const { stack } = event;
-		this.setStack(stack);
-	}
-
 	navigationButtonPressed = ({ buttonId }) => {
 		if (buttonId === 'cancel') {
 			const { componentId } = this.props;
@@ -196,37 +204,30 @@ export default class Sidebar extends Component {
 		});
 	}
 
-	setStack = async(stack) => {
-		const { stackRoot, setStackRoot } = this.props;
-		if (stackRoot !== stack) {
-			await Navigation.setStackRoot('AppRoot', {
-				component: {
-					id: stack,
-					name: stack
-				}
-			});
-			setStackRoot(stack);
-		}
-	}
-
-	closeDrawer = () => {
-		Navigation.toggleDrawer();
-	}
-
 	toggleStatus = () => {
 		LayoutAnimation.easeInEaseOut();
 		this.setState(prevState => ({ showStatus: !prevState.showStatus }));
 	}
 
-	sidebarNavigate = (stack) => {
-		this.closeDrawer();
-		this.setStack(stack);
+	sidebarNavigate = (route) => {
+		const { componentId } = this.props;
+		Navigation.push(componentId, {
+			component: {
+				name: route
+			}
+		});
+	}
+
+	logout = () => {
+		const { componentId, logout } = this.props;
+		Navigation.dismissModal(componentId);
+		logout();
 	}
 
 	renderSeparator = key => <View key={key} style={styles.separator} />;
 
 	renderItem = ({
-		text, left, onPress, testID, current
+		text, left, onPress, testID, disclosure
 	}) => (
 		<Touch
 			key={text}
@@ -235,13 +236,16 @@ export default class Sidebar extends Component {
 			activeOpacity={0.3}
 			testID={testID}
 		>
-			<View style={[styles.item, current && styles.itemSelected]}>
+			<View style={styles.item}>
 				<View style={styles.itemLeft}>
 					{left}
 				</View>
-				<Text style={styles.itemText}>
-					{text}
-				</Text>
+				<View style={styles.itemCenter}>
+					<Text style={styles.itemText}>
+						{text}
+					</Text>
+				</View>
+				{disclosure ? this.renderDisclosure() : null}
 			</View>
 		</Touch>
 	)
@@ -254,7 +258,6 @@ export default class Sidebar extends Component {
 				left: <View style={[styles.status, { backgroundColor: STATUS_COLORS[item.id] }]} />,
 				current: user.status === item.id,
 				onPress: () => {
-					this.closeDrawer();
 					this.toggleStatus();
 					if (user.status !== item.id) {
 						try {
@@ -268,43 +271,43 @@ export default class Sidebar extends Component {
 		);
 	}
 
-	renderNavigation = () => {
-		const { stackRoot } = this.props;
-		const { logout } = this.props;
-		return (
-			[
-				this.renderItem({
-					text: I18n.t('Chats'),
-					left: <Icon name='chat-bubble' size={20} />,
-					onPress: () => this.sidebarNavigate('RoomsListView'),
-					testID: 'sidebar-chats',
-					current: stackRoot === 'RoomsListView'
-				}),
-				this.renderItem({
-					text: I18n.t('Profile'),
-					left: <Icon name='person' size={20} />,
-					onPress: () => this.sidebarNavigate('ProfileView'),
-					testID: 'sidebar-profile',
-					current: stackRoot === 'ProfileView'
-				}),
-				this.renderItem({
-					text: I18n.t('Settings'),
-					left: <Icon name='settings' size={20} />,
-					onPress: () => this.sidebarNavigate('SettingsView'),
-					testID: 'sidebar-settings',
-					current: stackRoot === 'SettingsView'
-				}),
-				this.renderSeparator('separator-logout'),
-				this.renderItem({
-					text: I18n.t('Logout'),
-					left: <Icon name='exit-to-app' size={20} />,
-					onPress: () => logout(),
-					testID: 'sidebar-logout'
-				})
-			]
-		);
+	// Remove it after https://github.com/RocketChat/Rocket.Chat.ReactNative/pull/643
+	renderDisclosure = () => {
+		if (isIOS) {
+			return (
+				<View style={styles.disclosureContainer}>
+					<Image source={{ uri: 'disclosure_indicator' }} style={styles.disclosureIndicator} />
+				</View>
+			);
+		}
 	}
 
+	renderNavigation = () => (
+		[
+			this.renderItem({
+				text: I18n.t('Profile'),
+				left: <Icon name='person' size={20} />,
+				onPress: () => this.sidebarNavigate('ProfileView'),
+				testID: 'sidebar-profile',
+				disclosure: true
+			}),
+			this.renderItem({
+				text: I18n.t('Settings'),
+				left: <Icon name='settings' size={20} />,
+				onPress: () => this.sidebarNavigate('SettingsView'),
+				testID: 'sidebar-settings',
+				disclosure: true
+			}),
+			this.renderSeparator('separator-logout'),
+			this.renderItem({
+				text: I18n.t('Logout'),
+				left: <Icon name='exit-to-app' size={20} />,
+				onPress: () => this.logout(),
+				testID: 'sidebar-logout'
+			})
+		]
+	)
+
 	renderStatus = () => {
 		const { status } = this.state;
 		const { user } = this.props;
diff --git a/e2e/08-room.spec.js b/e2e/08-room.spec.js
index 6c25ee587..02fefdc52 100644
--- a/e2e/08-room.spec.js
+++ b/e2e/08-room.spec.js
@@ -169,7 +169,7 @@ describe('Room screen', () => {
 				await element(by.text(`${ data.random }message`)).longPress();
 				await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
 				await expect(element(by.text('Message actions'))).toBeVisible();
-				await element(by.text('Copy Permalink')).tap();
+				await element(by.text('Permalink')).tap();
 				await expect(element(by.text('Permalink copied to clipboard!'))).toBeVisible();
 				await waitFor(element(by.text('Permalink copied to clipboard!'))).toBeNotVisible().withTimeout(5000);
 				
@@ -180,7 +180,7 @@ describe('Room screen', () => {
 				await element(by.text(`${ data.random }message`)).longPress();
 				await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
 				await expect(element(by.text('Message actions'))).toBeVisible();
-				await element(by.text('Copy Message')).tap();
+				await element(by.text('Copy')).tap();
 				await expect(element(by.text('Copied to clipboard!'))).toBeVisible();
 				await waitFor(element(by.text('Copied to clipboard!'))).toBeNotVisible().withTimeout(5000);
 				// TODO: test clipboard
-- 
GitLab