diff --git a/app/containers/StatusModal.js b/app/containers/StatusModal.js
new file mode 100644
index 00000000000..f8341b12fed
--- /dev/null
+++ b/app/containers/StatusModal.js
@@ -0,0 +1,198 @@
+import React, { Component } from 'react';
+import {
+ View, Text, StyleSheet, ScrollView, TouchableHighlight
+} from 'react-native';
+import PropTypes from 'prop-types';
+import Modal from 'react-native-modal';
+import { responsive } from 'react-native-responsive-ui';
+
+import TextInput from './TextInput';
+import Button from './Button';
+import I18n from '../i18n';
+import sharedStyles from '../views/Styles';
+import { isIOS } from '../utils/deviceInfo';
+import { themes } from '../constants/colors';
+import { withTheme } from '../theme';
+import { withSplit } from '../split';
+
+const styles = StyleSheet.create({
+ modal: {
+ width: '100%',
+ alignItems: 'center',
+ margin: 0
+ },
+ titleContainer: {
+ flexDirection: 'row',
+ paddingHorizontal: 16,
+ paddingTop: 16
+ },
+ title: {
+ fontSize: 14,
+ ...sharedStyles.textBold
+ },
+ container: {
+ height: 230,
+ flexDirection: 'column'
+ },
+ scrollView: {
+ flex: 1,
+ padding: 16
+ },
+ buttonContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ padding: 16
+ },
+ button: {
+ marginBottom: 0
+ },
+ androidButton: {
+ paddingHorizontal: 15,
+ justifyContent: 'center',
+ height: 48,
+ borderRadius: 2
+ },
+ androidButtonText: {
+ fontSize: 18,
+ textAlign: 'center'
+ }
+
+});
+
+class StatusModal extends Component {
+ static propTypes = {
+ isVisible: PropTypes.bool,
+ close: PropTypes.func,
+ submit: PropTypes.func,
+ window: PropTypes.object,
+ theme: PropTypes.string,
+ statusText: PropTypes.string,
+ split: PropTypes.bool
+ }
+
+ state = {
+ statusText: '',
+ saving: false
+ };
+
+ componentDidMount() {
+ const { statusText } = this.props;
+ this.setState({ statusText });
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ const { statusText } = this.state;
+ const {
+ window, isVisible, split, theme
+ } = this.props;
+
+ if (nextState.statusText !== statusText) {
+ return true;
+ }
+ if (nextProps.split !== split) {
+ return true;
+ }
+ if (nextProps.theme !== theme) {
+ return true;
+ }
+ if (nextProps.isVisible !== isVisible) {
+ return true;
+ }
+ if (nextProps.window.width !== window.width) {
+ return true;
+ }
+ return false;
+ }
+
+ submit = () => {
+ this.setState({ saving: true });
+ const { submit } = this.props;
+ const { statusText } = this.state;
+ submit(statusText);
+ }
+
+ renderButtons = () => {
+ const { close, theme } = this.props;
+ const { saving } = this.state;
+ if (isIOS) {
+ return (
+
+
+
+
+ );
+ }
+ return (
+
+
+ {I18n.t('Cancel')}
+
+
+ {I18n.t('Set_status')}
+
+
+ );
+ }
+
+ render() {
+ const {
+ window: { width }, isVisible, close, split, theme
+ } = this.props;
+ const { statusText } = this.state;
+ return (
+
+
+
+ {I18n.t('Set_custom_status')}
+
+
+
+ this.setState({ statusText: value })}
+ theme={theme}
+ />
+
+ {this.renderButtons()}
+
+
+ );
+ }
+}
+
+export default responsive(withTheme(withSplit(StatusModal)));
diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js
index ebe96061f0b..b69cd33f778 100644
--- a/app/i18n/locales/en.js
+++ b/app/i18n/locales/en.js
@@ -10,6 +10,7 @@ export default {
'error-could-not-change-email': 'Could not change email',
'error-could-not-change-name': 'Could not change name',
'error-could-not-change-username': 'Could not change username',
+ 'error-could-not-change-status': 'Could not change status',
'error-delete-protected-role': 'Cannot delete a protected role',
'error-department-not-found': 'Department not found',
'error-direct-message-file-upload-not-allowed': 'File sharing not allowed in direct messages',
@@ -358,6 +359,9 @@ export default {
Server: 'Server',
Servers: 'Servers',
Server_version: 'Server version: {{version}}',
+ Set_custom_status: 'Set custom status',
+ Set_status: 'Set status',
+ Status_saved_successfully: 'Status saved successfully!',
Set_username_subtitle: 'The username is used to allow others to mention you in messages',
Settings: 'Settings',
Settings_succesfully_changed: 'Settings succesfully changed!',
@@ -378,6 +382,7 @@ export default {
Start_of_conversation: 'Start of conversation',
Started_discussion: 'Started a discussion:',
Started_call: 'Call started by {{userBy}}',
+ Status: 'Status',
Submit: 'Submit',
Table: 'Table',
Take_a_photo: 'Take a photo',
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index fb79d6b45f3..d6175bf9a0f 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -244,12 +244,12 @@ const RocketChat = {
}, 10000);
}
const userStatus = ddpMessage.fields.args[0];
- const [id,, status] = userStatus;
+ const [id,, status, statusText] = userStatus;
this.activeUsers[id] = STATUSES[status];
-
+ this.activeUsers[statusText] = statusText;
const { user: loggedUser } = reduxStore.getState().login;
if (loggedUser && loggedUser.id === id) {
- reduxStore.dispatch(setUser({ status: STATUSES[status] }));
+ reduxStore.dispatch(setUser({ status: STATUSES[status], statusText }));
}
}
}));
@@ -381,6 +381,7 @@ const RocketChat = {
name: result.me.name,
language: result.me.language,
status: result.me.status,
+ statusText: result.me.statusText,
customFields: result.me.customFields,
emails: result.me.emails,
roles: result.me.roles
diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js
index 1f89d017f14..927932f4960 100644
--- a/app/views/ProfileView/index.js
+++ b/app/views/ProfileView/index.js
@@ -65,7 +65,8 @@ class ProfileView extends React.Component {
avatarUrl: null,
avatar: {},
avatarSuggestions: {},
- customFields: {}
+ customFields: {},
+ statusText: null
}
async componentDidMount() {
@@ -103,7 +104,7 @@ class ProfileView extends React.Component {
init = (user) => {
const { user: userProps } = this.props;
const {
- name, username, emails, customFields
+ name, username, emails, customFields, statusText
} = user || userProps;
this.setState({
@@ -114,13 +115,14 @@ class ProfileView extends React.Component {
currentPassword: null,
avatarUrl: null,
avatar: {},
- customFields: customFields || {}
+ customFields: customFields || {},
+ statusText
});
}
formIsChanged = () => {
const {
- name, username, email, newPassword, avatar, customFields
+ name, username, email, newPassword, avatar, customFields, statusText
} = this.state;
const { user } = this.props;
let customFieldsChanged = false;
@@ -140,6 +142,7 @@ class ProfileView extends React.Component {
&& (user.emails && user.emails[0].address === email)
&& !avatar.data
&& !customFieldsChanged
+ && user.statusText === statusText
);
}
@@ -168,11 +171,16 @@ class ProfileView extends React.Component {
this.setState({ saving: true });
const {
- name, username, email, newPassword, currentPassword, avatar, customFields
+ name, username, email, newPassword, currentPassword, avatar, customFields, statusText
} = this.state;
const { user, setUser } = this.props;
const params = {};
+ // Status Text
+ if (user.statusText !== statusText) {
+ params.statusText = statusText;
+ }
+
// Name
if (user.name !== name) {
params.name = name;
@@ -377,7 +385,7 @@ class ProfileView extends React.Component {
render() {
const {
- name, username, email, newPassword, avatarUrl, customFields, avatar, saving, showPasswordAlert
+ name, username, email, newPassword, avatarUrl, customFields, avatar, saving, showPasswordAlert, statusText
} = this.state;
const {
baseUrl, user, theme, Accounts_CustomFields
@@ -406,6 +414,16 @@ class ProfileView extends React.Component {
token={user.token}
/>
+ { this.statusText = e; }}
+ label={I18n.t('Status')}
+ placeholder={I18n.t('Status')}
+ value={statusText}
+ onChangeText={value => this.setState({ statusText: value })}
+ onSubmitEditing={() => { this.statusText.focus(); }}
+ testID='profile-view-status'
+ theme={theme}
+ />
{ this.name = e; }}
label={I18n.t('Name')}
@@ -503,7 +521,8 @@ const mapStateToProps = state => ({
username: state.login.user && state.login.user.username,
customFields: state.login.user && state.login.user.customFields,
emails: state.login.user && state.login.user.emails,
- token: state.login.user && state.login.user.token
+ token: state.login.user && state.login.user.token,
+ statusText: state.login.user && state.login.user.statusText
},
Accounts_CustomFields: state.settings.Accounts_CustomFields,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js
index 363c6cf2397..b9681d158d7 100644
--- a/app/views/RoomActionsView/index.js
+++ b/app/views/RoomActionsView/index.js
@@ -42,7 +42,8 @@ class RoomActionsView extends React.Component {
navigation: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
- token: PropTypes.string
+ token: PropTypes.string,
+ statusText: PropTypes.string
}),
leaveRoom: PropTypes.func,
jitsiEnabled: PropTypes.bool,
@@ -62,7 +63,8 @@ class RoomActionsView extends React.Component {
joined: !!room,
canViewMembers: false,
canAutoTranslate: false,
- canAddUser: false
+ canAddUser: false,
+ userStatusText: null
};
if (room && room.observe && room.rid) {
this.roomObservable = room.observe();
@@ -90,7 +92,16 @@ class RoomActionsView extends React.Component {
log(e);
}
}
-
+ if (room && room.t === 'd') {
+ const { user } = this.props;
+ try {
+ const roomUserId = await RocketChat.getRoomMemberId(this.rid, user.id);
+ const result = await RocketChat.getUserInfo(roomUserId);
+ this.setState({ userStatusText: result.user.statusText });
+ } catch (e) {
+ log(e);
+ }
+ }
if (room && room.t !== 'd' && this.canViewMembers()) {
try {
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
@@ -397,7 +408,7 @@ class RoomActionsView extends React.Component {
}
renderRoomInfo = ({ item }) => {
- const { room, member } = this.state;
+ const { room, member, userStatusText } = this.state;
const { name, t, topic } = room;
const { baseUrl, user, theme } = this.props;
@@ -426,6 +437,8 @@ class RoomActionsView extends React.Component {
)
}
{t === 'd' ? `@${ name }` : topic}
+
+ {t === 'd' ? {userStatusText} : null }
,
], item)
@@ -500,7 +513,8 @@ class RoomActionsView extends React.Component {
const mapStateToProps = state => ({
user: {
id: state.login.user && state.login.user.id,
- token: state.login.user && state.login.user.token
+ token: state.login.user && state.login.user.token,
+ statusText: state.login.user && state.login.user.statusText
},
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
jitsiEnabled: state.settings.Jitsi_Enabled || false
diff --git a/app/views/RoomActionsView/styles.js b/app/views/RoomActionsView/styles.js
index 1461cba3785..f954185082d 100644
--- a/app/views/RoomActionsView/styles.js
+++ b/app/views/RoomActionsView/styles.js
@@ -58,5 +58,11 @@ export default StyleSheet.create({
roomTitleRow: {
flexDirection: 'row',
alignItems: 'center'
+ },
+ statusText: {
+ paddingTop: 5,
+ fontSize: 14,
+ ...sharedStyles.textColorDescription,
+ ...sharedStyles.textMedium
}
});
diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js
index f7c2cbd4e9c..fa86a0c6a81 100644
--- a/app/views/RoomInfoView/index.js
+++ b/app/views/RoomInfoView/index.js
@@ -299,7 +299,9 @@ class RoomInfoView extends React.Component {
{this.renderAvatar(room, roomUser)}
{ getRoomTitle(room, this.t, roomUser && roomUser.name, theme) }
+ {this.t === 'd' ? {roomUser.statusText} : null }
+
{this.isDirect() ? this.renderDirect() : this.renderChannel()}
@@ -311,7 +313,8 @@ const mapStateToProps = state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
user: {
id: state.login.user && state.login.user.id,
- token: state.login.user && state.login.user.token
+ token: state.login.user && state.login.user.token,
+ statusText: state.login.user && state.login.user.statusText
},
Message_TimeFormat: state.settings.Message_TimeFormat
});
diff --git a/app/views/RoomInfoView/styles.js b/app/views/RoomInfoView/styles.js
index 00901410a52..a9574767717 100644
--- a/app/views/RoomInfoView/styles.js
+++ b/app/views/RoomInfoView/styles.js
@@ -66,5 +66,11 @@ export default StyleSheet.create({
role: {
fontSize: 14,
...sharedStyles.textRegular
+ },
+ statusText: {
+ paddingTop: 10,
+ fontSize: 18,
+ ...sharedStyles.textColorDescription,
+ ...sharedStyles.textMedium
}
});
diff --git a/app/views/RoomView/Header/index.js b/app/views/RoomView/Header/index.js
index 4911be2d3a5..39c23746b88 100644
--- a/app/views/RoomView/Header/index.js
+++ b/app/views/RoomView/Header/index.js
@@ -9,6 +9,7 @@ import RightButtons from './RightButtons';
import { withTheme } from '../../../theme';
import RoomHeaderLeft from './RoomHeaderLeft';
+
class RoomHeaderView extends Component {
static propTypes = {
title: PropTypes.string,
@@ -62,7 +63,6 @@ class RoomHeaderView extends Component {
const {
window, title, type, prid, tmid, widthOffset, status = 'offline', connecting, usersTyping, goRoomActionsView, theme
} = this.props;
-
return (
{
+ const { theme } = this.props;
+ const { statusText } = this.state;
+ if (this.t === 'd') {
+ return (
+
+ {statusText || ''}
+
+ );
+ } else {
+ return null;
+ }
+ }
+
renderFooter = () => {
const {
joined, room, selectedMessage, editing, replying, replyWithMention
@@ -874,7 +898,6 @@ class RoomView extends React.Component {
} = this.state;
const { user, baseUrl, theme } = this.props;
const { rid, t } = room;
-
return (
+ {this.renderStatusText()}
+
{this.renderFooter()}
{this.renderActions()}
({
user: {
id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username,
- token: state.login.user && state.login.user.token
+ token: state.login.user && state.login.user.token,
+ statusText: state.login.user && state.login.user.statusText
},
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
useRealName: state.settings.UI_Use_Real_Name,
diff --git a/app/views/RoomView/styles.js b/app/views/RoomView/styles.js
index 9f011413ebd..893c811be52 100644
--- a/app/views/RoomView/styles.js
+++ b/app/views/RoomView/styles.js
@@ -12,6 +12,15 @@ export default StyleSheet.create({
list: {
flex: 1
},
+ statusTextContainer: {
+ padding: 10,
+ justifyContent: 'flex-end',
+ alignItems: 'center'
+ },
+ statusText: {
+ fontSize: 14,
+ ...sharedStyles.textMedium
+ },
contentContainer: {
paddingTop: 10
},
@@ -45,6 +54,7 @@ export default StyleSheet.create({
},
previewMode: {
fontSize: 16,
- ...sharedStyles.textMedium
+ ...sharedStyles.textMedium,
+ ...sharedStyles.textColorNormal
}
});
diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js
index 2d7d784ce84..af36d713e25 100644
--- a/app/views/SidebarView/index.js
+++ b/app/views/SidebarView/index.js
@@ -20,9 +20,12 @@ import styles from './styles';
import SidebarItem from './SidebarItem';
import { themes } from '../../constants/colors';
import database from '../../lib/database';
+import { LISTENER } from '../../containers/Toast';
+import EventEmitter from '../../utils/events';
import { animateNextTransition } from '../../utils/layoutAnimation';
import { withTheme } from '../../theme';
import { withSplit } from '../../split';
+import StatusModal from '../../containers/StatusModal';
const keyExtractor = item => item.id;
@@ -56,7 +59,8 @@ class Sidebar extends Component {
this.state = {
showStatus: false,
isAdmin: false,
- status: []
+ status: [],
+ showStatusModal: false
};
}
@@ -76,19 +80,27 @@ class Sidebar extends Component {
}
shouldComponentUpdate(nextProps, nextState) {
- const { status, showStatus, isAdmin } = this.state;
+ const {
+ status, showStatus, isAdmin, showStatusModal
+ } = this.state;
const {
Site_Name, user, baseUrl, activeItemKey, split, theme
} = this.props;
if (nextState.showStatus !== showStatus) {
return true;
}
+ if (nextState.showStatusModal !== showStatusModal) {
+ return true;
+ }
if (nextProps.Site_Name !== Site_Name) {
return true;
}
if (nextProps.Site_Name !== Site_Name) {
return true;
}
+ if (nextProps.user.statusText !== user.statusText) {
+ return true;
+ }
if (nextProps.baseUrl !== baseUrl) {
return true;
}
@@ -105,6 +117,9 @@ class Sidebar extends Component {
if (nextProps.user.status !== user.status) {
return true;
}
+ if (nextProps.user.statusText !== user.statusText) {
+ return true;
+ }
if (nextProps.user.username !== user.username) {
return true;
}
@@ -139,6 +154,17 @@ class Sidebar extends Component {
});
}
+ setStatusText = async(statusText) => {
+ const result = await RocketChat.saveUserProfile({ statusText }, {});
+ if (result.success) {
+ EventEmitter.emit(LISTENER, { message: I18n.t('Status_saved_successfully') });
+ } else {
+ EventEmitter.emit(LISTENER, { message: I18n.t('error-could-not-change-status') });
+ }
+ this.setState({ showStatusModal: false });
+ }
+
+
async setIsAdmin() {
const db = database.active;
const { user } = this.props;
@@ -157,6 +183,10 @@ class Sidebar extends Component {
}
}
+ setStatusModal = () => {
+ this.setState({ showStatusModal: true });
+ }
+
logout = () => {
const { logout } = this.props;
logout();
@@ -198,6 +228,7 @@ class Sidebar extends Component {
const { activeItemKey, theme } = this.props;
return (
<>
+
}
@@ -239,6 +270,21 @@ class Sidebar extends Component {
);
}
+ renderStatusText = () => {
+ const { activeItemKey, theme, user } = this.props;
+ return (
+
+ }
+ onPress={this.setStatusModal}
+ testID='sidebar-status'
+ current={activeItemKey === 'StatusStack'}
+ />
+
+ );
+ }
+
renderStatus = () => {
const { status } = this.state;
const { user } = this.props;
@@ -253,7 +299,7 @@ class Sidebar extends Component {
}
render() {
- const { showStatus } = this.state;
+ const { showStatus, showStatusModal } = this.state;
const {
user, Site_Name, baseUrl, split, theme
} = this.props;
@@ -300,9 +346,18 @@ class Sidebar extends Component {
{!split || showStatus ? : null}
+ {this.renderStatusText()}
+
{!showStatus && !split ? this.renderNavigation() : null}
+
{showStatus ? this.renderStatus() : null}
+ this.setState({ showStatusModal: false })}
+ statusText={user.statusText}
+ submit={statusText => this.setStatusText(statusText)}
+ />
);
}
@@ -314,6 +369,7 @@ const mapStateToProps = state => ({
id: state.login.user && state.login.user.id,
language: state.login.user && state.login.user.language,
status: state.login.user && state.login.user.status,
+ statusText: state.login.user && state.login.user.statusText,
username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token,
roles: state.login.user && state.login.user.roles