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
This commit is contained in:
Diego Mello 2019-02-27 11:26:40 -03:00 committed by GitHub
parent 2ef2be51d3
commit f3ddf60a57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 144 additions and 188 deletions

View File

@ -18,26 +18,17 @@ const startLogged = () => {
Navigation.loadView('RoomHeaderView'); Navigation.loadView('RoomHeaderView');
Navigation.loadView('SettingsView'); Navigation.loadView('SettingsView');
Navigation.loadView('SidebarView'); Navigation.loadView('SidebarView');
Navigation.setRoot({ Navigation.setRoot({
root: { root: {
sideMenu: { stack: {
left: { id: 'AppRoot',
children: [{
component: { component: {
id: 'SidebarView', id: 'RoomsListView',
name: 'SidebarView' name: 'RoomsListView'
} }
}, }]
center: {
stack: {
id: 'AppRoot',
children: [{
component: {
id: 'RoomsListView',
name: 'RoomsListView'
}
}]
}
}
} }
} }
}); });

View File

@ -6,11 +6,12 @@ import {
import Navigation from '../lib/Navigation'; import Navigation from '../lib/Navigation';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { appStart } from '../actions'; import { appStart } from '../actions';
import { serverFinishAdd } from '../actions/server'; import { serverFinishAdd, selectServerRequest } from '../actions/server';
import { loginFailure, loginSuccess } from '../actions/login'; import { loginFailure, loginSuccess } from '../actions/login';
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 database from '../lib/realm';
const getServer = state => state.server.server; const getServer = state => state.server.server;
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args); const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
@ -60,8 +61,26 @@ const handleLogout = function* handleLogout() {
if (server) { if (server) {
try { try {
yield call(logoutCall, { server }); 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')); yield put(appStart('outside'));
} catch (e) { } catch (e) {
yield put(appStart('outside'));
log('handleLogout', e); log('handleLogout', e);
} }
} }

View File

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { View, ScrollView, Keyboard } from 'react-native';
View, ScrollView, Keyboard, BackHandler
} from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Dialog from 'react-native-dialog'; import Dialog from 'react-native-dialog';
import SHA256 from 'js-sha256'; 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 SafeAreaView from 'react-native-safe-area-view';
import equal from 'deep-equal'; import equal from 'deep-equal';
import Navigation from '../../lib/Navigation';
import LoggedView from '../View'; import LoggedView from '../View';
import KeyboardView from '../../presentation/KeyboardView'; import KeyboardView from '../../presentation/KeyboardView';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
@ -26,9 +23,7 @@ import I18n from '../../i18n';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { appStart as appStartAction } from '../../actions';
import { setUser as setUserAction } from '../../actions/login'; import { setUser as setUserAction } from '../../actions/login';
import Icons from '../../lib/Icons';
@connect(state => ({ @connect(state => ({
user: { user: {
@ -42,7 +37,6 @@ import Icons from '../../lib/Icons';
Accounts_CustomFields: state.settings.Accounts_CustomFields, Accounts_CustomFields: state.settings.Accounts_CustomFields,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}), dispatch => ({ }), dispatch => ({
appStart: () => dispatch(appStartAction()),
setUser: params => dispatch(setUserAction(params)) setUser: params => dispatch(setUserAction(params))
})) }))
/** @extends React.Component */ /** @extends React.Component */
@ -50,32 +44,17 @@ export default class ProfileView extends LoggedView {
static options() { static options() {
return { return {
topBar: { topBar: {
leftButtons: [{
id: 'settings',
icon: Icons.getSource('settings'),
testID: 'rooms-list-view-sidebar'
}],
title: { title: {
text: I18n.t('Profile') text: I18n.t('Profile')
} }
},
sideMenu: {
left: {
enabled: true
},
right: {
enabled: true
}
} }
}; };
} }
static propTypes = { static propTypes = {
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
componentId: PropTypes.string,
user: PropTypes.object, user: PropTypes.object,
Accounts_CustomFields: PropTypes.string, Accounts_CustomFields: PropTypes.string,
appStart: PropTypes.func,
setUser: PropTypes.func setUser: PropTypes.func
} }
@ -94,8 +73,6 @@ export default class ProfileView extends LoggedView {
avatarSuggestions: {}, avatarSuggestions: {},
customFields: {} customFields: {}
}; };
Navigation.events().bindComponent(this);
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
} }
async componentDidMount() { async componentDidMount() {
@ -126,22 +103,6 @@ export default class ProfileView extends LoggedView {
return false; 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) => { setAvatar = (avatar) => {
this.setState({ avatar }); this.setState({ avatar });
} }

View File

@ -81,6 +81,13 @@ export default class ServerDropdown extends Component {
} }
} }
componentWillUnmount() {
if (this.newServerTimeout) {
clearTimeout(this.newServerTimeout);
this.newServerTimeout = false;
}
}
updateState = () => { updateState = () => {
const { servers } = this; const { servers } = this;
this.setState({ servers }); this.setState({ servers });
@ -137,12 +144,7 @@ export default class ServerDropdown extends Component {
const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`); const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
if (!token) { if (!token) {
appStart(); appStart();
try { this.newServerTimeout = setTimeout(() => {
this.sdk.disconnect();
} catch (error) {
console.warn(error);
}
setTimeout(() => {
EventEmitter.emit('NewServer', { server }); EventEmitter.emit('NewServer', { server });
}, 1000); }, 1000);
} else { } else {

View File

@ -270,7 +270,22 @@ export default class RoomsListView extends LoggedView {
} }
}); });
} else if (buttonId === 'settings') { } else if (buttonId === 'settings') {
Navigation.toggleDrawer(); Navigation.showModal({
stack: {
children: [{
component: {
name: 'SidebarView',
options: {
topBar: {
title: {
text: I18n.t('Settings')
}
}
}
}
}]
}
});
} else if (buttonId === 'search') { } else if (buttonId === 'search') {
this.initSearchingAndroid(); this.initSearchingAndroid();
} else if (buttonId === 'back') { } else if (buttonId === 'back') {

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; 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 RNPickerSelect from 'react-native-picker-select';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view'; import SafeAreaView from 'react-native-safe-area-view';
@ -18,36 +18,20 @@ import Loading from '../../containers/Loading';
import { showErrorAlert, showToast } from '../../utils/info'; import { showErrorAlert, showToast } from '../../utils/info';
import log from '../../utils/log'; import log from '../../utils/log';
import { setUser as setUserAction } from '../../actions/login'; import { setUser as setUserAction } from '../../actions/login';
import { appStart as appStartAction } from '../../actions';
import Icons from '../../lib/Icons';
@connect(state => ({ @connect(state => ({
userLanguage: state.login.user && state.login.user.language userLanguage: state.login.user && state.login.user.language
}), dispatch => ({ }), dispatch => ({
setUser: params => dispatch(setUserAction(params)), setUser: params => dispatch(setUserAction(params))
appStart: () => dispatch(appStartAction())
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class SettingsView extends LoggedView { export default class SettingsView extends LoggedView {
static options() { static options() {
return { return {
topBar: { topBar: {
leftButtons: [{
id: 'settings',
icon: Icons.getSource('settings'),
testID: 'rooms-list-view-sidebar'
}],
title: { title: {
text: I18n.t('Settings') text: I18n.t('Settings')
} }
},
sideMenu: {
left: {
enabled: true
},
right: {
enabled: true
}
} }
}; };
} }
@ -55,8 +39,7 @@ export default class SettingsView extends LoggedView {
static propTypes = { static propTypes = {
componentId: PropTypes.string, componentId: PropTypes.string,
userLanguage: PropTypes.string, userLanguage: PropTypes.string,
setUser: PropTypes.func, setUser: PropTypes.func
appStart: PropTypes.func
} }
constructor(props) { constructor(props) {
@ -85,8 +68,6 @@ export default class SettingsView extends LoggedView {
}], }],
saving: false saving: false
}; };
Navigation.events().bindComponent(this);
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
@ -104,22 +85,6 @@ export default class SettingsView extends LoggedView {
return false; 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) => { getLabel = (language) => {
const { languages } = this.state; const { languages } = this.state;
const l = languages.find(i => i.value === language); const l = languages.find(i => i.value === language);

View File

@ -1,14 +1,13 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView, Image
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import equal from 'deep-equal'; import equal from 'deep-equal';
import Navigation from '../lib/Navigation'; import Navigation from '../lib/Navigation';
import { setStackRoot as setStackRootAction } from '../actions';
import { logout as logoutAction } from '../actions/login'; import { logout as logoutAction } from '../actions/login';
import Avatar from '../containers/Avatar'; import Avatar from '../containers/Avatar';
import Status from '../containers/status'; import Status from '../containers/status';
@ -18,7 +17,8 @@ import RocketChat from '../lib/rocketchat';
import log from '../utils/log'; import log from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
import scrollPersistTaps from '../utils/scrollPersistTaps'; 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({ const styles = StyleSheet.create({
container: { container: {
@ -34,14 +34,14 @@ const styles = StyleSheet.create({
width: 30, width: 30,
alignItems: 'center' alignItems: 'center'
}, },
itemCenter: {
flex: 1
},
itemText: { itemText: {
marginVertical: 16, marginVertical: 16,
fontWeight: 'bold', fontWeight: 'bold',
color: '#292E35' color: '#292E35'
}, },
itemSelected: {
backgroundColor: '#F7F8FA'
},
separator: { separator: {
borderBottomWidth: StyleSheet.hairlineWidth, borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: '#ddd', borderColor: '#ddd',
@ -79,13 +79,22 @@ const styles = StyleSheet.create({
fontWeight: '600', fontWeight: '600',
color: '#292E35', color: '#292E35',
fontSize: 13 fontSize: 13
},
disclosureContainer: {
marginLeft: 6,
marginRight: 9,
alignItems: 'center',
justifyContent: 'center'
},
disclosureIndicator: {
width: 20,
height: 20
} }
}); });
const keyExtractor = item => item.id; const keyExtractor = item => item.id;
@connect(state => ({ @connect(state => ({
Site_Name: state.settings.Site_Name, Site_Name: state.settings.Site_Name,
stackRoot: state.app.stackRoot,
user: { user: {
id: state.login.user && state.login.user.id, id: state.login.user && state.login.user.id,
language: state.login.user && state.login.user.language, 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 : '' baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}), dispatch => ({ }), dispatch => ({
logout: () => dispatch(logoutAction()), logout: () => dispatch(logoutAction())
setStackRoot: stackRoot => dispatch(setStackRootAction(stackRoot))
})) }))
export default class Sidebar extends Component { export default class Sidebar extends Component {
static options() {
return {
topBar: {
leftButtons: [{
id: 'cancel',
icon: isAndroid ? Icons.getSource('close', false) : undefined,
systemItem: 'cancel'
}]
}
};
}
static propTypes = { static propTypes = {
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
componentId: PropTypes.string, componentId: PropTypes.string,
Site_Name: PropTypes.string.isRequired, Site_Name: PropTypes.string.isRequired,
stackRoot: PropTypes.string.isRequired,
user: PropTypes.object, user: PropTypes.object,
logout: PropTypes.func.isRequired, logout: PropTypes.func.isRequired
setStackRoot: PropTypes.func
} }
constructor(props) { constructor(props) {
@ -131,18 +149,13 @@ export default class Sidebar extends Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { status, showStatus } = this.state; const { status, showStatus } = this.state;
const { const { Site_Name, user, baseUrl } = this.props;
Site_Name, stackRoot, user, baseUrl
} = this.props;
if (nextState.showStatus !== showStatus) { if (nextState.showStatus !== showStatus) {
return true; return true;
} }
if (nextProps.Site_Name !== Site_Name) { if (nextProps.Site_Name !== Site_Name) {
return true; return true;
} }
if (nextProps.stackRoot !== stackRoot) {
return true;
}
if (nextProps.Site_Name !== Site_Name) { if (nextProps.Site_Name !== Site_Name) {
return true; return true;
} }
@ -166,11 +179,6 @@ export default class Sidebar extends Component {
return false; return false;
} }
handleChangeStack = (event) => {
const { stack } = event;
this.setStack(stack);
}
navigationButtonPressed = ({ buttonId }) => { navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'cancel') { if (buttonId === 'cancel') {
const { componentId } = this.props; 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 = () => { toggleStatus = () => {
LayoutAnimation.easeInEaseOut(); LayoutAnimation.easeInEaseOut();
this.setState(prevState => ({ showStatus: !prevState.showStatus })); this.setState(prevState => ({ showStatus: !prevState.showStatus }));
} }
sidebarNavigate = (stack) => { sidebarNavigate = (route) => {
this.closeDrawer(); const { componentId } = this.props;
this.setStack(stack); Navigation.push(componentId, {
component: {
name: route
}
});
}
logout = () => {
const { componentId, logout } = this.props;
Navigation.dismissModal(componentId);
logout();
} }
renderSeparator = key => <View key={key} style={styles.separator} />; renderSeparator = key => <View key={key} style={styles.separator} />;
renderItem = ({ renderItem = ({
text, left, onPress, testID, current text, left, onPress, testID, disclosure
}) => ( }) => (
<Touch <Touch
key={text} key={text}
@ -235,13 +236,16 @@ export default class Sidebar extends Component {
activeOpacity={0.3} activeOpacity={0.3}
testID={testID} testID={testID}
> >
<View style={[styles.item, current && styles.itemSelected]}> <View style={styles.item}>
<View style={styles.itemLeft}> <View style={styles.itemLeft}>
{left} {left}
</View> </View>
<Text style={styles.itemText}> <View style={styles.itemCenter}>
{text} <Text style={styles.itemText}>
</Text> {text}
</Text>
</View>
{disclosure ? this.renderDisclosure() : null}
</View> </View>
</Touch> </Touch>
) )
@ -254,7 +258,6 @@ export default class Sidebar extends Component {
left: <View style={[styles.status, { backgroundColor: STATUS_COLORS[item.id] }]} />, left: <View style={[styles.status, { backgroundColor: STATUS_COLORS[item.id] }]} />,
current: user.status === item.id, current: user.status === item.id,
onPress: () => { onPress: () => {
this.closeDrawer();
this.toggleStatus(); this.toggleStatus();
if (user.status !== item.id) { if (user.status !== item.id) {
try { try {
@ -268,43 +271,43 @@ export default class Sidebar extends Component {
); );
} }
renderNavigation = () => { // Remove it after https://github.com/RocketChat/Rocket.Chat.ReactNative/pull/643
const { stackRoot } = this.props; renderDisclosure = () => {
const { logout } = this.props; if (isIOS) {
return ( return (
[ <View style={styles.disclosureContainer}>
this.renderItem({ <Image source={{ uri: 'disclosure_indicator' }} style={styles.disclosureIndicator} />
text: I18n.t('Chats'), </View>
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'
})
]
);
} }
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 = () => { renderStatus = () => {
const { status } = this.state; const { status } = this.state;
const { user } = this.props; const { user } = this.props;

View File

@ -169,7 +169,7 @@ describe('Room screen', () => {
await element(by.text(`${ data.random }message`)).longPress(); await element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Message actions'))).toBeVisible(); 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 expect(element(by.text('Permalink copied to clipboard!'))).toBeVisible();
await waitFor(element(by.text('Permalink copied to clipboard!'))).toBeNotVisible().withTimeout(5000); 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 element(by.text(`${ data.random }message`)).longPress();
await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000);
await expect(element(by.text('Message actions'))).toBeVisible(); 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 expect(element(by.text('Copied to clipboard!'))).toBeVisible();
await waitFor(element(by.text('Copied to clipboard!'))).toBeNotVisible().withTimeout(5000); await waitFor(element(by.text('Copied to clipboard!'))).toBeNotVisible().withTimeout(5000);
// TODO: test clipboard // TODO: test clipboard