From af5c3efbd2a4431f3969f4ac60bd70a2fc361c34 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 5 Dec 2017 17:57:44 -0200 Subject: [PATCH] Manage user's status (#128) * Init custom header * Change user status --- app/actions/actionsTypes.js | 1 + app/actions/login.js | 7 + app/constants/colors.js | 6 + app/containers/Header.js | 212 ++++++++++++++++++++++++++++ app/containers/routes/AuthRoutes.js | 3 +- app/lib/rocketchat.js | 8 ++ app/reducers/login.js | 8 ++ app/views/RoomsListView.js | 18 --- 8 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 app/containers/Header.js diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 2593bd09..4e44be0d 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -26,6 +26,7 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [ ...defaultTypes, 'INIT' ]); +export const USER = createRequestTypes('USER', ['SET']); export const ROOMS = createRequestTypes('ROOMS'); export const ROOM = createRequestTypes('ROOM', ['ADD_USER_TYPING', 'REMOVE_USER_TYPING', 'SOMEONE_TYPING', 'OPEN', 'USER_TYPING']); export const APP = createRequestTypes('APP', ['READY', 'INIT']); diff --git a/app/actions/login.js b/app/actions/login.js index 4c53c301..d2a31cb5 100644 --- a/app/actions/login.js +++ b/app/actions/login.js @@ -118,3 +118,10 @@ export function forgotPasswordFailure(err) { err }; } + +export function setUser(action) { + return { + type: types.USER.SET, + ...action + }; +} diff --git a/app/constants/colors.js b/app/constants/colors.js index 145e665b..276ceda2 100644 --- a/app/constants/colors.js +++ b/app/constants/colors.js @@ -1,2 +1,8 @@ export const AVATAR_COLORS = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B']; export const ESLINT_FIX = null; +export const STATUS_COLORS = { + online: '#2de0a5', + busy: '#f5455c', + away: '#ffd21f', + offline: '#cbced1' +}; diff --git a/app/containers/Header.js b/app/containers/Header.js new file mode 100644 index 00000000..4ffa7d9e --- /dev/null +++ b/app/containers/Header.js @@ -0,0 +1,212 @@ +import React from 'react'; +import { Text, View, StyleSheet, Platform, TouchableOpacity, Dimensions } from 'react-native'; +import Icon from 'react-native-vector-icons/Ionicons'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import Modal from 'react-native-modal'; +import { SafeAreaView } from 'react-navigation'; + +import DrawerMenuButton from '../presentation/DrawerMenuButton'; +import Avatar from './Avatar'; +import RocketChat from '../lib/rocketchat'; +import { STATUS_COLORS } from '../constants/colors'; + +const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56; + +let platformContainerStyles; +if (Platform.OS === 'ios') { + platformContainerStyles = { + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: 'rgba(0, 0, 0, .3)' + }; +} else { + platformContainerStyles = { + shadowColor: 'black', + shadowOpacity: 0.1, + shadowRadius: StyleSheet.hairlineWidth, + shadowOffset: { + height: StyleSheet.hairlineWidth + }, + elevation: 4 + }; +} + +const appBarHeight = Platform.OS === 'ios' ? 44 : 56; +const { width } = Dimensions.get('window'); +const styles = StyleSheet.create({ + container: { + backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF', + height: appBarHeight, + ...platformContainerStyles + }, + appBar: { + flex: 1 + }, + header: { + flexDirection: 'row', + alignItems: 'center', + flex: 1 + }, + titleContainer: { + left: TITLE_OFFSET, + right: TITLE_OFFSET, + position: 'absolute', + alignItems: 'center', + justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start', + flexDirection: 'row' + }, + status: { + borderRadius: 4, + width: 8, + height: 8, + marginRight: 10 + }, + avatar: { + marginRight: 10 + }, + title: { + fontWeight: 'bold' + }, + left: { + left: 0, + position: 'absolute' + }, + right: { + right: 0, + position: 'absolute' + }, + modal: { + width: width - 60, + height: width - 60, + backgroundColor: '#F7F7F7', + borderRadius: 4, + flexDirection: 'column' + }, + modalButton: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'transparent', + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: 'rgba(0, 0, 0, .3)', + paddingHorizontal: 20 + } +}); + +@connect(state => ({ + user: state.login.user, + Site_Url: state.settings.Site_Url +})) +export default class extends React.PureComponent { + static propTypes = { + navigation: PropTypes.object.isRequired, + user: PropTypes.object.isRequired, + Site_Url: PropTypes.string + } + + constructor(props) { + super(props); + this.state = { + isModalVisible: false + }; + } + + onPressModalButton(status) { + RocketChat.setUserPresenceDefaultStatus(status); + this.hideModal(); + } + + showModal() { + this.setState({ isModalVisible: true }); + } + + hideModal() { + this.setState({ isModalVisible: false }); + } + + createChannel() { + this.props.navigation.navigate('SelectUsers'); + } + + renderTitle() { + if (!this.props.user.username) { + return null; + } + return ( + this.showModal()}> + + + {this.props.user.username} + + ); + } + + renderRight() { + if (Platform.OS !== 'ios') { + return; + } + return ( + + this.createChannel()} + /> + + ); + } + + renderModalButton = (status, text) => { + const statusStyle = [styles.status, { backgroundColor: STATUS_COLORS[status] }]; + const textStyle = { flex: 1, fontWeight: this.props.user.status === status ? 'bold' : 'normal' }; + return ( + this.onPressModalButton(status)} + > + + + {text || status.charAt(0).toUpperCase() + status.slice(1)} + + + ); + }; + + render() { + return ( + + + + + + + {this.renderTitle()} + {this.renderRight()} + + + this.hideModal()} + onBackdropPress={() => this.hideModal()} + > + + {this.renderModalButton('online')} + {this.renderModalButton('busy')} + {this.renderModalButton('away')} + {this.renderModalButton('offline', 'Invisible')} + + + + ); + } +} diff --git a/app/containers/routes/AuthRoutes.js b/app/containers/routes/AuthRoutes.js index d39bc8a6..95cbd0a9 100644 --- a/app/containers/routes/AuthRoutes.js +++ b/app/containers/routes/AuthRoutes.js @@ -4,6 +4,7 @@ import { StackNavigator, DrawerNavigator } from 'react-navigation'; import Sidebar from '../../containers/Sidebar'; import DrawerMenuButton from '../../presentation/DrawerMenuButton'; +import Header from '../../containers/Header'; import RoomsListView from '../../views/RoomsListView'; import RoomView from '../../views/RoomView'; @@ -20,7 +21,7 @@ const AuthRoutes = StackNavigator( navigationOptions({ navigation }) { return { title: 'Rooms', - [drawerIconPosition]: + header:
}; } }, diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 885900b6..4b2c6abe 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -9,6 +9,7 @@ import settingsType from '../constants/settings'; import realm from './realm'; import * as actions from '../actions'; import { someoneTyping } from '../actions/room'; +import { setUser } from '../actions/login'; import { disconnect, connectSuccess } from '../actions/connect'; export { Accounts } from 'react-native-meteor'; @@ -94,6 +95,9 @@ const RocketChat = { }); } } + if (ddpMessage.collection === 'users') { + return reduxStore.dispatch(setUser({ status: ddpMessage.fields.status || ddpMessage.fields.statusDefault })); + } }); RocketChat.getSettings(); RocketChat.getPermissions(); @@ -432,6 +436,7 @@ const RocketChat = { }); Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false); Meteor.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false); + Meteor.subscribe('userData', null, false); return data; }, logout({ server }) { @@ -526,6 +531,9 @@ const RocketChat = { }, setUserPresenceOnline() { return call('UserPresence:online'); + }, + setUserPresenceDefaultStatus(status) { + return call('UserPresence:setDefaultStatus', status); } }; diff --git a/app/reducers/login.js b/app/reducers/login.js index 83b3187b..37d72ef6 100644 --- a/app/reducers/login.js +++ b/app/reducers/login.js @@ -107,6 +107,14 @@ export default function login(state = initialState, action) { failure: true, error: action.err }; + case types.USER.SET: + return { + ...state, + user: { + ...state.user, + ...action + } + }; default: return state; } diff --git a/app/views/RoomsListView.js b/app/views/RoomsListView.js index a9277ec9..ecf68643 100644 --- a/app/views/RoomsListView.js +++ b/app/views/RoomsListView.js @@ -79,24 +79,6 @@ export default class RoomsListView extends React.Component { server: PropTypes.string } - static navigationOptions = ({ navigation }) => { - if (Platform.OS !== 'ios') { - return; - } - - const { params = {} } = navigation.state; - const headerRight = ( - ); - - return { headerRight }; - }; - constructor(props) { super(props);