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('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'
}
}]
}
}
}]
}
}
});

View File

@ -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);
}
}

View File

@ -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 });
}

View File

@ -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 {

View File

@ -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') {

View File

@ -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);

View File

@ -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;

View File

@ -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