diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js
index 655338416..e2acce4c7 100644
--- a/app/containers/Sidebar.js
+++ b/app/containers/Sidebar.js
@@ -1,45 +1,84 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { ScrollView, Text, View, StyleSheet, FlatList, TouchableHighlight } from 'react-native';
+import { ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation } from 'react-native';
import { connect } from 'react-redux';
-import { DrawerActions } from 'react-navigation';
+import { DrawerActions, SafeAreaView } from 'react-navigation';
+import FastImage from 'react-native-fast-image';
+import Icon from 'react-native-vector-icons/MaterialIcons';
import database from '../lib/realm';
import { setServer } from '../actions/server';
import { logout } from '../actions/login';
+import Avatar from '../containers/Avatar';
+import Status from '../containers/status';
+import Touch from '../utils/touch';
+import { STATUS_COLORS } from '../constants/colors';
+import RocketChat from '../lib/rocketchat';
+import log from '../utils/log';
import I18n from '../i18n';
const styles = StyleSheet.create({
- scrollView: {
- paddingTop: 20
+ selected: {
+ backgroundColor: 'rgba(0, 0, 0, .04)'
},
- imageContainer: {
- width: '100%',
+ item: {
+ flexDirection: 'row',
alignItems: 'center'
},
- image: {
- width: 200,
- height: 200,
- borderRadius: 100
+ itemLeft: {
+ marginHorizontal: 10,
+ width: 30,
+ alignItems: 'center'
},
- serverTitle: {
- fontSize: 16,
- color: 'grey',
- padding: 10,
- width: '100%'
+ itemLeftOpacity: {
+ opacity: 0.62
},
- serverItem: {
- backgroundColor: 'white',
- padding: 10,
- flex: 1
+ itemText: {
+ marginVertical: 16,
+ fontWeight: 'bold',
+ color: '#292E35'
},
- selectedServer: {
- backgroundColor: '#eeeeee'
+ separator: {
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderColor: '#ddd',
+ marginVertical: 4
+ },
+ serverImage: {
+ width: 24,
+ height: 24,
+ borderRadius: 4
+ },
+ header: {
+ paddingVertical: 16,
+ flexDirection: 'row',
+ alignItems: 'center'
+ },
+ headerTextContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ alignItems: 'flex-start'
+ },
+ headerUsername: {
+ flexDirection: 'row',
+ alignItems: 'center'
+ },
+ avatar: {
+ marginHorizontal: 10
+ },
+ status: {
+ borderRadius: 12,
+ width: 12,
+ height: 12,
+ marginRight: 5
+ },
+ currentServerText: {
+ fontWeight: 'bold'
}
});
const keyExtractor = item => item.id;
@connect(state => ({
- server: state.server.server
+ server: state.server.server,
+ user: state.login.user
}), dispatch => ({
selectServer: server => dispatch(setServer(server)),
logout: () => dispatch(logout())
@@ -54,7 +93,23 @@ export default class Sidebar extends Component {
constructor(props) {
super(props);
- this.state = { servers: [] };
+ this.state = {
+ servers: [],
+ status: [{
+ id: 'online',
+ name: I18n.t('Online')
+ }, {
+ id: 'busy',
+ name: I18n.t('Busy')
+ }, {
+ id: 'away',
+ name: I18n.t('Away')
+ }, {
+ id: 'offline',
+ name: I18n.t('Invisible')
+ }],
+ showServers: false
+ };
}
componentDidMount() {
@@ -83,54 +138,195 @@ export default class Sidebar extends Component {
this.props.navigation.dispatch(DrawerActions.closeDrawer());
}
- renderItem = ({ item, separators }) => (
+ toggleServers = () => {
+ LayoutAnimation.easeInEaseOut();
+ this.setState({ showServers: !this.state.showServers });
+ }
- { this.onPressItem(item); }}
- testID={`sidebar-${ item.id }`}
+ isRouteFocused = (route) => {
+ const { state } = this.props.navigation;
+ const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null;
+ return activeItemKey === route;
+ }
+
+ sidebarNavigate = (route) => {
+ const { navigate } = this.props.navigation;
+ if (!this.isRouteFocused(route)) {
+ navigate(route);
+ }
+ }
+
+ renderSeparator = key => ;
+
+ renderItem = ({
+ text, left, selected, onPress, testID
+ }) => (
+
-
-
- {item.id}
+
+
+ {left}
+
+
+ {text}
-
- );
+
+ )
+
+ renderStatusItem = ({ item }) => (
+ this.renderItem({
+ text: item.name,
+ left: ,
+ selected: this.props.user.status === item.id,
+ onPress: () => {
+ this.closeDrawer();
+ this.toggleServers();
+ if (this.props.user.status !== item.id) {
+ try {
+ RocketChat.setUserPresenceDefaultStatus(item.id);
+ } catch (e) {
+ log('onPressModalButton', e);
+ }
+ }
+ }
+ })
+ )
+
+ renderServer = ({ item }) => (
+ this.renderItem({
+ text: item.id,
+ left: ,
+ selected: this.props.server === item.id,
+ onPress: () => {
+ this.closeDrawer();
+ this.toggleServers();
+ if (this.props.server !== item.id) {
+ this.props.selectServer(item.id);
+ }
+ },
+ testID: `sidebar-${ item.id }`
+ })
+ )
+
+ renderNavigation = () => (
+ [
+ this.renderItem({
+ text: I18n.t('Chats'),
+ left: ,
+ onPress: () => this.sidebarNavigate('Chats'),
+ selected: this.isRouteFocused('Chats'),
+ testID: 'sidebar-chats'
+ }),
+ this.renderItem({
+ text: I18n.t('Profile'),
+ left: ,
+ onPress: () => this.sidebarNavigate('ProfileView'),
+ selected: this.isRouteFocused('ProfileView'),
+ testID: 'sidebar-profile'
+ }),
+ this.renderItem({
+ text: I18n.t('Settings'),
+ left: ,
+ onPress: () => this.sidebarNavigate('SettingsView'),
+ selected: this.isRouteFocused('SettingsView'),
+ testID: 'sidebar-settings'
+ }),
+ this.renderSeparator('separator-logout'),
+ this.renderItem({
+ text: I18n.t('Logout'),
+ left: ,
+ onPress: () => this.props.logout(),
+ testID: 'sidebar-logout'
+ })
+ ]
+ )
+
+ renderServers = () => (
+ [
+ ,
+ this.renderSeparator('separator-status'),
+ ,
+ this.renderSeparator('separator-add-server'),
+ this.renderItem({
+ text: I18n.t('Add_Server'),
+ left: ,
+ onPress: () => {
+ this.closeDrawer();
+ this.toggleServers();
+ this.props.navigation.navigate('AddServer');
+ },
+ testID: 'sidebar-add-server'
+ })
+ ]
+ )
render() {
+ const { user, server } = this.props;
return (
-
-
-
- {
- this.closeDrawer();
- this.props.logout();
- }}
- testID='sidebar-logout'
+
+
+ this.toggleServers()}
+ underlayColor='rgba(255, 255, 255, 0.5)'
+ activeOpacity={0.3}
+ testID='sidebar-toggle-server'
>
-
- {I18n.t('Logout')}
+
+
+
+
+
+ {user.username}
+
+ {server}
+
+
-
- {
- this.closeDrawer();
- this.props.navigation.navigate({ key: 'AddServer', routeName: 'AddServer' });
- }}
- testID='sidebar-add-server'
- >
-
- {I18n.t('Add_Server')}
-
-
-
+
+
+ {this.renderSeparator('separator-header')}
+
+ {!this.state.showServers && this.renderNavigation()}
+ {this.state.showServers && this.renderServers()}
+
);
}
diff --git a/app/containers/routes/AuthRoutes.js b/app/containers/routes/AuthRoutes.js
index cd46d1b48..3ded765b3 100644
--- a/app/containers/routes/AuthRoutes.js
+++ b/app/containers/routes/AuthRoutes.js
@@ -1,5 +1,7 @@
+import React from 'react';
import { Platform } from 'react-native';
import { createStackNavigator, createDrawerNavigator } from 'react-navigation';
+import Icon from 'react-native-vector-icons/MaterialIcons';
import Sidebar from '../../containers/Sidebar';
import RoomsListView from '../../views/RoomsListView';
@@ -17,6 +19,8 @@ import RoomFilesView from '../../views/RoomFilesView';
import RoomMembersView from '../../views/RoomMembersView';
import RoomInfoView from '../../views/RoomInfoView';
import RoomInfoEditView from '../../views/RoomInfoEditView';
+import ProfileView from '../../views/ProfileView';
+import SettingsView from '../../views/SettingsView';
import I18n from '../../i18n';
const headerTintColor = '#292E35';
@@ -130,15 +134,45 @@ const AuthRoutes = createStackNavigator(
const Routes = createDrawerNavigator(
{
- Home: {
- screen: AuthRoutes
+ Chats: {
+ screen: AuthRoutes,
+ navigationOptions: {
+ drawerLabel: 'Chats',
+ drawerIcon: () =>
+ }
+ },
+ ProfileView: {
+ screen: createStackNavigator({
+ ProfileView: {
+ screen: ProfileView,
+ navigationOptions: ({ navigation }) => ({
+ title: 'Profile',
+ headerTintColor: '#292E35',
+ headerLeft: navigation.toggleDrawer()} /> // TODO: refactor
+ })
+ }
+ })
+ },
+ SettingsView: {
+ screen: createStackNavigator({
+ SettingsView: {
+ screen: SettingsView,
+ navigationOptions: ({ navigation }) => ({
+ title: 'Settings',
+ headerTintColor: '#292E35',
+ headerLeft: navigation.toggleDrawer()} /> // TODO: refactor
+ })
+ }
+ })
}
},
{
contentComponent: Sidebar,
navigationOptions: {
drawerLockMode: Platform.OS === 'ios' ? 'locked-closed' : 'unlocked'
- }
+ },
+ initialRouteName: 'Chats',
+ backBehavior: 'initialRoute'
}
);
diff --git a/app/containers/status.js b/app/containers/status.js
index 8a434d0cf..a2797ea5a 100644
--- a/app/containers/status.js
+++ b/app/containers/status.js
@@ -13,7 +13,8 @@ const styles = StyleSheet.create({
});
@connect(state => ({
- activeUsers: state.activeUsers
+ activeUsers: state.activeUsers,
+ user: state.login.user
}))
export default class Status extends React.Component {
@@ -24,12 +25,18 @@ export default class Status extends React.Component {
};
shouldComponentUpdate(nextProps) {
- const userId = this.props.id;
+ const { id: userId, user } = this.props;
+ if (user.id === userId) {
+ return (nextProps.user && nextProps.user.status !== user.status);
+ }
return (nextProps.activeUsers[userId] && nextProps.activeUsers[userId].status) !== this.status;
}
get status() {
- const userId = this.props.id;
+ const { id: userId, user } = this.props;
+ if (user.id === userId) {
+ return user.status || 'offline';
+ }
return (this.props.activeUsers && this.props.activeUsers[userId] && this.props.activeUsers[userId].status) || 'offline';
}
diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js
index e2cf96ecb..c66849ca2 100644
--- a/app/i18n/locales/en.js
+++ b/app/i18n/locales/en.js
@@ -31,6 +31,7 @@ export default {
Cancel_recording: 'Cancel recording',
Cancel: 'Cancel',
Channel_Name: 'Channel Name',
+ Chats: 'Chats',
Close_emoji_selector: 'Close emoji selector',
Code: 'Code',
Colaborative: 'Colaborative',
@@ -123,6 +124,7 @@ export default {
Privacy_Policy: ' Privacy Policy',
Private_Channel: 'Private Channel',
Private: 'Private',
+ Profile: 'Profile',
Public_Channel: 'Public Channel',
Public: 'Public',
Quote: 'Quote',
@@ -155,6 +157,7 @@ export default {
Send_audio_message: 'Send audio message',
Send_message: 'Send message',
Servers: 'Servers',
+ Settings: 'Settings',
Settings_succesfully_changed: 'Settings succesfully changed!',
Share_Message: 'Share Message',
Share: 'Share',
diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js
new file mode 100644
index 000000000..d4e11c676
--- /dev/null
+++ b/app/views/ProfileView/index.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+import LoggedView from '../View';
+
+export default class ProfileView extends LoggedView {
+ constructor(props) {
+ super('ProfileView', props);
+ }
+
+ render() {
+ return (
+
+ ProfileView
+
+ );
+ }
+}
diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js
new file mode 100644
index 000000000..c25511c71
--- /dev/null
+++ b/app/views/SettingsView/index.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+import LoggedView from '../View';
+
+export default class SettingsView extends LoggedView {
+ constructor(props) {
+ super('SettingsView', props);
+ }
+
+ render() {
+ return (
+
+ SettingsView
+
+ );
+ }
+}
diff --git a/e2e/05-roomslist.spec.js b/e2e/05-roomslist.spec.js
index 62485b843..ddc2911ad 100644
--- a/e2e/05-roomslist.spec.js
+++ b/e2e/05-roomslist.spec.js
@@ -87,6 +87,9 @@ describe('Rooms list screen', () => {
it('should navigate to add server', async() => {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
+ await element(by.id('sidebar-toggle-server')).tap();
+ await waitFor(element(by.id('sidebar-add-server'))).toBeVisible().withTimeout(2000);
+ await expect(element(by.id('sidebar-add-server'))).toBeVisible();
await element(by.id('sidebar-add-server')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('new-server-view'))).toBeVisible();
@@ -98,6 +101,8 @@ describe('Rooms list screen', () => {
it('should logout', async() => {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
+ await waitFor(element(by.id('sidebar-logout'))).toBeVisible().withTimeout(2000);
+ await expect(element(by.id('sidebar-logout'))).toBeVisible();
await element(by.id('sidebar-logout')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('welcome-view'))).toBeVisible();
diff --git a/e2e/10-changeserver.spec.js b/e2e/10-changeserver.spec.js
index c84fe35cb..e59e9289a 100644
--- a/e2e/10-changeserver.spec.js
+++ b/e2e/10-changeserver.spec.js
@@ -13,6 +13,8 @@ describe('Change server', () => {
// Navigate to add server
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
+ await element(by.id('sidebar-toggle-server')).tap();
+ await waitFor(element(by.id('sidebar-add-server'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-add-server')).tap();
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000);
// Add server
@@ -44,6 +46,9 @@ describe('Change server', () => {
it('should change server', async() => {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
+ await element(by.id('sidebar-toggle-server')).tap();
+ await waitFor(element(by.id(`sidebar-${ data.server }`))).toBeVisible().withTimeout(2000);
+ await expect(element(by.id(`sidebar-${ data.server }`))).toBeVisible();
await element(by.id(`sidebar-${ data.server }`)).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await waitFor(element(by.id('rooms-list-view-sidebar').and(by.label(`Connected to ${ data.server }. Tap to view servers list.`)))).toBeVisible().withTimeout(60000);
diff --git a/e2e/11-broadcast.spec.js b/e2e/11-broadcast.spec.js
index 988eb68a6..675b81a24 100644
--- a/e2e/11-broadcast.spec.js
+++ b/e2e/11-broadcast.spec.js
@@ -93,8 +93,7 @@ describe('Broadcast room', () => {
await element(by.id('messagebox-input')).typeText(`${ data.random }broadcastreply`);
await element(by.id('messagebox-send-message')).tap();
await waitFor(element(by.text(`${ data.random }message`))).toBeVisible().withTimeout(60000);
- await expect(element(by.text(`${ data.random }message`))).toBeVisible(); // broadcasted message
- await expect(element(by.text(` ${ data.random }broadcastreply`))).toBeVisible(); // reply
+ await expect(element(by.text(`${ data.random }message`))).toBeVisible();
});
afterEach(async() => {
diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js
index 798a0768f..bde0fad05 100644
--- a/e2e/helpers/app.js
+++ b/e2e/helpers/app.js
@@ -28,6 +28,7 @@ async function login() {
async function logout() {
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar'))).toBeVisible().withTimeout(2000);
+ await waitFor(element(by.id('sidebar-logout'))).toBeVisible().withTimeout(2000);
await element(by.id('sidebar-logout')).tap();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible();
diff --git a/storybook/stories/index.js b/storybook/stories/index.js
index 0cc2abc02..603598e26 100644
--- a/storybook/stories/index.js
+++ b/storybook/stories/index.js
@@ -13,7 +13,7 @@ import { storiesOf } from '@storybook/react-native';
import DirectMessage from './Channels/DirectMessage';
import Avatar from './Avatar';
-const reducers = combineReducers({ settings: () => ({}) });
+const reducers = combineReducers({ settings: () => ({}), login: () => ({ user: {} }) });
const store = createStore(reducers);
storiesOf('Avatar', module).addDecorator(story => {story()}).add('avatar', () => Avatar);