From 5d8ad1df82d3d200abd53bfb68c3e8008656e05a Mon Sep 17 00:00:00 2001
From: Diego Mello <diegolmello@gmail.com>
Date: Thu, 29 Mar 2018 14:55:37 -0300
Subject: [PATCH] [NEW] Room info and Room info edit (#254)

* - Block user
- Load room members async
- fixed reactive change of room's read only flag

* Snippet messages

* - Room files
- Dismiss Video component on back button press
- Improvements on Image component

* Improvement on Video component

* Leave room

* Missing message types

* lint

* - Room info (read only)
- Missing message types

* Room info scroll

* - Tap on room header opens room info
- Layout tweaks

* - Room info edit
- iOS Toast fixed

* - Style not implemented actions as disabled

* Edit room permission

* - Save all room settings in a single call
- Implemented roomType and readOnly

* - Allow reacting when room is read only

* Message type added: room_changed_privacy

* Erase room

* Created TextInput and SwitchContainer components for reuse and readability

* - hasPermission method

* - Archive/Unarchive room
- Set Join Code

* Twitter keyboard type on iOS

* Archived room

* reactWhenReadOnly permission on message

* Active users refactored

* User roles

* - Subscribe to roles (in order to get role description info: e.g. 'core-team' to 'Rocket.Chat Team')
- Save roles to realm (for offline access)
- Save roles to redux (and get data from realm on app init)

* Lint

* code style
---
 app/actions/actionsTypes.js                   |   4 +-
 app/actions/activeUsers.js                    |   7 -
 app/actions/roles.js                          |   8 +
 app/actions/room.js                           |   7 +
 app/constants/colors.js                       |   3 +-
 app/containers/MessageActions.js              |  23 +-
 app/containers/MessageBox/index.js            |   1 +
 app/containers/TextInput.js                   |  76 ++++
 app/containers/message/index.js               |  34 +-
 app/containers/routes/AuthRoutes.js           |  16 +
 app/containers/status.js                      |   4 +-
 app/lib/realm.js                              |  19 +-
 app/lib/rocketchat.js                         |  91 ++++-
 app/reducers/index.js                         |   2 +
 app/reducers/roles.js                         |  15 +
 app/sagas/activeUsers.js                      |  13 -
 app/sagas/index.js                            |   2 -
 app/sagas/init.js                             |   6 +
 app/sagas/rooms.js                            |  33 +-
 app/views/RoomActionsView/index.js            |  62 ++-
 app/views/RoomActionsView/styles.js           |  13 +-
 app/views/RoomInfoEditView/SwitchContainer.js |  45 +++
 app/views/RoomInfoEditView/index.js           | 381 ++++++++++++++++++
 app/views/RoomInfoEditView/styles.js          |  46 +++
 app/views/RoomInfoView/index.js               | 192 +++++++++
 app/views/RoomInfoView/styles.js              |  70 ++++
 app/views/RoomMembersView/index.js            |   3 +-
 app/views/RoomMembersView/styles.js           |  14 +-
 app/views/RoomView/Header/index.js            |  10 +-
 app/views/RoomView/index.js                   |   3 +-
 app/views/RoomsListView/index.js              |   7 +-
 app/views/Styles.js                           |  35 +-
 ios/RocketChatRN.xcodeproj/project.pbxproj    |  33 ++
 33 files changed, 1170 insertions(+), 108 deletions(-)
 create mode 100644 app/actions/roles.js
 create mode 100644 app/containers/TextInput.js
 create mode 100644 app/reducers/roles.js
 delete mode 100644 app/sagas/activeUsers.js
 create mode 100644 app/views/RoomInfoEditView/SwitchContainer.js
 create mode 100644 app/views/RoomInfoEditView/index.js
 create mode 100644 app/views/RoomInfoEditView/styles.js
 create mode 100644 app/views/RoomInfoView/index.js
 create mode 100644 app/views/RoomInfoView/styles.js

diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index 2f1f95a30..f4daf9a81 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -39,6 +39,7 @@ export const ROOM = createRequestTypes('ROOM', [
 	'OPEN',
 	'CLOSE',
 	'LEAVE',
+	'ERASE',
 	'USER_TYPING',
 	'MESSAGE_RECEIVED',
 	'SET_LAST_OPEN',
@@ -93,7 +94,8 @@ export const SERVER = createRequestTypes('SERVER', [
 ]);
 export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']);
 export const LOGOUT = 'LOGOUT'; // logout is always success
-export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST']);
+export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET']);
+export const ROLES = createRequestTypes('ROLES', ['SET']);
 export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNSTARRED']);
 export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']);
 export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
diff --git a/app/actions/activeUsers.js b/app/actions/activeUsers.js
index a273d7348..1e7c5ecb7 100644
--- a/app/actions/activeUsers.js
+++ b/app/actions/activeUsers.js
@@ -1,12 +1,5 @@
 import * as types from './actionsTypes';
 
-export function requestActiveUser(users) {
-	return {
-		type: types.ACTIVE_USERS.REQUEST,
-		users
-	};
-}
-
 export function setActiveUser(data) {
 	return {
 		type: types.ACTIVE_USERS.SET,
diff --git a/app/actions/roles.js b/app/actions/roles.js
new file mode 100644
index 000000000..074111985
--- /dev/null
+++ b/app/actions/roles.js
@@ -0,0 +1,8 @@
+import * as types from './actionsTypes';
+
+export function setRoles(data) {
+	return {
+		type: types.ROLES.SET,
+		data
+	};
+}
diff --git a/app/actions/room.js b/app/actions/room.js
index 533fb4ab3..ef6430c3b 100644
--- a/app/actions/room.js
+++ b/app/actions/room.js
@@ -42,6 +42,13 @@ export function leaveRoom(rid) {
 	};
 }
 
+export function eraseRoom(rid) {
+	return {
+		type: types.ROOM.ERASE,
+		rid
+	};
+}
+
 export function userTyping(status = true) {
 	return {
 		type: types.ROOM.USER_TYPING,
diff --git a/app/constants/colors.js b/app/constants/colors.js
index 276ceda23..5caf08c58 100644
--- a/app/constants/colors.js
+++ b/app/constants/colors.js
@@ -1,8 +1,9 @@
 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 COLOR_DANGER = '#f5455c';
 export const STATUS_COLORS = {
 	online: '#2de0a5',
-	busy: '#f5455c',
+	busy: COLOR_DANGER,
 	away: '#ffd21f',
 	offline: '#cbced1'
 };
diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js
index 6a2de4b4a..0a82d50ff 100644
--- a/app/containers/MessageActions.js
+++ b/app/containers/MessageActions.js
@@ -17,8 +17,8 @@ import {
 	toggleReactionPicker
 } from '../actions/messages';
 import { showToast } from '../utils/info';
+import RocketChat from '../lib/rocketchat';
 
-const returnAnArray = obj => obj || [];
 @connect(
 	state => ({
 		showActions: state.messages.showActions,
@@ -79,10 +79,6 @@ export default class MessageActions extends React.Component {
 		};
 		this.handleActionPress = this.handleActionPress.bind(this);
 		this.options = [''];
-		const { roles } = this.props.room;
-		const roomRoles = Array.from(Object.keys(roles), i => roles[i].value);
-		const userRoles = this.props.user.roles || [];
-		this.mergedRoles = [...new Set([...roomRoles, ...userRoles])];
 		this.setPermissions(this.props.permissions);
 	}
 
@@ -127,7 +123,7 @@ export default class MessageActions extends React.Component {
 				this.PIN_INDEX = this.options.length - 1;
 			}
 			// Reaction
-			if (!this.isRoomReadOnly()) {
+			if (!this.isRoomReadOnly() || this.canReactWhenReadOnly()) {
 				this.options.push('Add Reaction');
 				this.REACTION_INDEX = this.options.length - 1;
 			}
@@ -171,19 +167,20 @@ export default class MessageActions extends React.Component {
 		this.setPermissions(this.props.permissions);
 	}
 
-	setPermissions(permissions) {
-		this.hasEditPermission = returnAnArray(permissions['edit-message'])
-			.some(item => this.mergedRoles.indexOf(item) !== -1);
-		this.hasDeletePermission = returnAnArray(permissions['delete-message'])
-			.some(item => this.mergedRoles.indexOf(item) !== -1);
-		this.hasForceDeletePermission = returnAnArray(permissions['force-delete-message'])
-			.some(item => this.mergedRoles.indexOf(item) !== -1);
+	setPermissions() {
+		const permissions = ['edit-message', 'delete-message', 'force-delete-message'];
+		const result = RocketChat.hasPermission(permissions, this.props.room.rid);
+		this.hasEditPermission = result[permissions[0]];
+		this.hasDeletePermission = result[permissions[1]];
+		this.hasForceDeletePermission = result[permissions[2]];
 	}
 
 	isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
 
 	isRoomReadOnly = () => this.props.room.ro;
 
+	canReactWhenReadOnly = () => this.props.room.reactWhenReadOnly;
+
 	allowEdit = (props) => {
 		if (this.isRoomReadOnly()) {
 			return false;
diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js
index 313e7740e..1a320fe49 100644
--- a/app/containers/MessageBox/index.js
+++ b/app/containers/MessageBox/index.js
@@ -489,6 +489,7 @@ export default class MessageBox extends React.PureComponent {
 							ref={component => this.component = component}
 							style={styles.textBoxInput}
 							returnKeyType='default'
+							keyboardType='twitter'
 							blurOnSubmit={false}
 							placeholder='New Message'
 							onChangeText={text => this.onChangeText(text)}
diff --git a/app/containers/TextInput.js b/app/containers/TextInput.js
new file mode 100644
index 000000000..73f2ccabf
--- /dev/null
+++ b/app/containers/TextInput.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { View, StyleSheet, Text, TextInput } from 'react-native';
+import PropTypes from 'prop-types';
+
+import sharedStyles from '../views/Styles';
+import { COLOR_DANGER } from '../constants/colors';
+
+const styles = StyleSheet.create({
+	inputContainer: {
+		marginBottom: 20
+	},
+	label: {
+		marginBottom: 4,
+		fontSize: 16
+	},
+	input: {
+		paddingTop: 12,
+		paddingBottom: 12,
+		paddingHorizontal: 10,
+		borderWidth: 2,
+		borderRadius: 2,
+		backgroundColor: 'white',
+		borderColor: 'rgba(0,0,0,.15)',
+		color: 'black'
+	},
+	labelError: {
+		color: COLOR_DANGER
+	},
+	inputError: {
+		color: COLOR_DANGER,
+		borderColor: COLOR_DANGER
+	}
+});
+
+export default class RCTextInput extends React.PureComponent {
+	static propTypes = {
+		label: PropTypes.string,
+		value: PropTypes.string,
+		error: PropTypes.object,
+		inputProps: PropTypes.object,
+		inputRef: PropTypes.func,
+		onChangeText: PropTypes.func,
+		onSubmitEditing: PropTypes.func
+	}
+
+	static defaultProps = {
+		label: 'Label',
+		error: {}
+	}
+
+	render() {
+		const {
+			label, value, error, inputRef, onChangeText, onSubmitEditing, inputProps
+		} = this.props;
+		return (
+			<View style={styles.inputContainer}>
+				<Text style={[styles.label, error.error && styles.labelError]}>
+					{label}
+				</Text>
+				<TextInput
+					ref={inputRef}
+					style={[styles.input, error.error && styles.inputError]}
+					onChangeText={onChangeText}
+					onSubmitEditing={onSubmitEditing}
+					value={value}
+					autoCorrect={false}
+					returnKeyType='next'
+					autoCapitalize='none'
+					underlineColorAndroid='transparent'
+					{...inputProps}
+				/>
+				{error.error && <Text style={sharedStyles.error}>{error.reason}</Text>}
+			</View>
+		);
+	}
+}
diff --git a/app/containers/message/index.js b/app/containers/message/index.js
index ab3e9a3fb..babf15cec 100644
--- a/app/containers/message/index.js
+++ b/app/containers/message/index.js
@@ -46,12 +46,14 @@ export default class Message extends React.Component {
 		onReactionPress: PropTypes.func,
 		style: ViewPropTypes.style,
 		onLongPress: PropTypes.func,
-		_updatedAt: PropTypes.instanceOf(Date)
+		_updatedAt: PropTypes.instanceOf(Date),
+		archived: PropTypes.bool
 	}
 
 	static defaultProps = {
 		onLongPress: () => {},
-		_updatedAt: new Date()
+		_updatedAt: new Date(),
+		archived: false
 	}
 
 	constructor(props) {
@@ -121,6 +123,14 @@ export default class Message extends React.Component {
 			message = `${ msg } was set ${ role } by ${ u.username }`;
 		} else if (t === 'subscription-role-removed') {
 			message = `${ msg } is no longer ${ role } by ${ u.username }`;
+		} else if (t === 'room_changed_description') {
+			message = `Room description changed to: ${ msg } by ${ u.username }`;
+		} else if (t === 'room_changed_announcement') {
+			message = `Room announcement changed to: ${ msg } by ${ u.username }`;
+		} else if (t === 'room_changed_topic') {
+			message = `Room topic changed to: ${ msg } by ${ u.username }`;
+		} else if (t === 'room_changed_privacy') {
+			message = `Room type changed to: ${ msg } by ${ u.username }`;
 		}
 
 		return message;
@@ -130,7 +140,21 @@ export default class Message extends React.Component {
 
 	isInfoMessage() {
 		return [
-			'r', 'au', 'ru', 'ul', 'uj', 'rm', 'user-muted', 'user-unmuted', 'message_pinned', 'subscription-role-added', 'subscription-role-removed'
+			'r',
+			'au',
+			'ru',
+			'ul',
+			'uj',
+			'rm',
+			'user-muted',
+			'user-unmuted',
+			'message_pinned',
+			'subscription-role-added',
+			'subscription-role-removed',
+			'room_changed_description',
+			'room_changed_announcement',
+			'room_changed_topic',
+			'room_changed_privacy'
 		].includes(this.props.item.t);
 	}
 
@@ -236,7 +260,7 @@ export default class Message extends React.Component {
 
 	render() {
 		const {
-			item, message, editing, baseUrl, customEmojis, style
+			item, message, editing, baseUrl, customEmojis, style, archived
 		} = this.props;
 		const username = item.alias || item.u.username;
 		const isEditing = message._id === item._id && editing;
@@ -246,7 +270,7 @@ export default class Message extends React.Component {
 			<TouchableHighlight
 				onPress={() => this.onPress()}
 				onLongPress={() => this.onLongPress()}
-				disabled={this.isDeleted() || this.hasError()}
+				disabled={this.isDeleted() || this.hasError() || archived}
 				underlayColor='#FFFFFF'
 				activeOpacity={0.3}
 				style={[styles.message, isEditing ? styles.editing : null, style]}
diff --git a/app/containers/routes/AuthRoutes.js b/app/containers/routes/AuthRoutes.js
index 9dd5cab43..e0d8c0d2a 100644
--- a/app/containers/routes/AuthRoutes.js
+++ b/app/containers/routes/AuthRoutes.js
@@ -14,6 +14,8 @@ import MentionedMessagesView from '../../views/MentionedMessagesView';
 import SnippetedMessagesView from '../../views/SnippetedMessagesView';
 import RoomFilesView from '../../views/RoomFilesView';
 import RoomMembersView from '../../views/RoomMembersView';
+import RoomInfoView from '../../views/RoomInfoView';
+import RoomInfoEditView from '../../views/RoomInfoEditView';
 
 const AuthRoutes = StackNavigator(
 	{
@@ -89,6 +91,20 @@ const AuthRoutes = StackNavigator(
 				title: 'Room Members',
 				headerTintColor: '#292E35'
 			}
+		},
+		RoomInfo: {
+			screen: RoomInfoView,
+			navigationOptions: {
+				title: 'Room Info',
+				headerTintColor: '#292E35'
+			}
+		},
+		RoomInfoEdit: {
+			screen: RoomInfoEditView,
+			navigationOptions: {
+				title: 'Room Info Edit',
+				headerTintColor: '#292E35'
+			}
 		}
 	},
 	{
diff --git a/app/containers/status.js b/app/containers/status.js
index 55a78fcc1..8a434d0cf 100644
--- a/app/containers/status.js
+++ b/app/containers/status.js
@@ -25,12 +25,12 @@ export default class Status extends React.Component {
 
 	shouldComponentUpdate(nextProps) {
 		const userId = this.props.id;
-		return this.status !== nextProps.activeUsers[userId];
+		return (nextProps.activeUsers[userId] && nextProps.activeUsers[userId].status) !== this.status;
 	}
 
 	get status() {
 		const userId = this.props.id;
-		return (this.props.activeUsers && this.props.activeUsers[userId]) || 'offline';
+		return (this.props.activeUsers && this.props.activeUsers[userId] && this.props.activeUsers[userId].status) || 'offline';
 	}
 
 	render() {
diff --git a/app/lib/realm.js b/app/lib/realm.js
index ec0fa3824..8ad8d5db7 100644
--- a/app/lib/realm.js
+++ b/app/lib/realm.js
@@ -80,8 +80,6 @@ const subscriptionSchema = {
 		roles: { type: 'list', objectType: 'subscriptionRolesSchema' },
 		unread: { type: 'int', optional: true },
 		userMentions: { type: 'int', optional: true },
-		// userMentions: 0,
-		// groupMentions: 0,
 		roomUpdatedAt: { type: 'date', optional: true },
 		ro: { type: 'bool', optional: true },
 		lastOpen: { type: 'date', optional: true },
@@ -89,7 +87,10 @@ const subscriptionSchema = {
 		description: { type: 'string', optional: true },
 		announcement: { type: 'string', optional: true },
 		topic: { type: 'string', optional: true },
-		blocked: { type: 'bool', optional: true }
+		blocked: { type: 'bool', optional: true },
+		reactWhenReadOnly: { type: 'bool', optional: true },
+		archived: { type: 'bool', optional: true },
+		joinCodeRequired: { type: 'bool', optional: true }
 	}
 };
 
@@ -237,6 +238,15 @@ const customEmojisSchema = {
 	}
 };
 
+const rolesSchema = {
+	name: 'roles',
+	primaryKey: '_id',
+	properties: {
+		_id: 'string',
+		description: { type: 'string', optional: true }
+	}
+};
+
 const schema = [
 	settingsSchema,
 	subscriptionSchema,
@@ -254,7 +264,8 @@ const schema = [
 	customEmojiAliasesSchema,
 	customEmojisSchema,
 	messagesReactionsSchema,
-	messagesReactionsUsernamesSchema
+	messagesReactionsUsernamesSchema,
+	rolesSchema
 ];
 class DB {
 	databases = {
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 1ea5c6d60..97b00d3b9 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -12,12 +12,13 @@ import * as actions from '../actions';
 import { someoneTyping, roomMessageReceived } from '../actions/room';
 import { setUser, setLoginServices, removeLoginServices } from '../actions/login';
 import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect';
-import { requestActiveUser } from '../actions/activeUsers';
+import { setActiveUser } from '../actions/activeUsers';
 import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
 import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
 import { mentionedMessagesReceived } from '../actions/mentionedMessages';
 import { snippetedMessagesReceived } from '../actions/snippetedMessages';
 import { roomFilesReceived } from '../actions/roomFiles';
+import { setRoles } from '../actions/roles';
 import Ddp from './ddp';
 
 export { Accounts } from 'react-native-meteor';
@@ -26,6 +27,7 @@ const call = (method, ...params) => RocketChat.ddp.call(method, ...params); // e
 const TOKEN_KEY = 'reactnativemeteor_usertoken';
 const SERVER_TIMEOUT = 30000;
 
+const returnAnArray = obj => obj || [];
 
 const normalizeMessage = (lastMessage) => {
 	if (lastMessage) {
@@ -91,13 +93,12 @@ const RocketChat = {
 			this._setUserTimer = null;
 		}
 
-
 		this._setUserTimer = setTimeout(() => {
-			reduxStore.dispatch(requestActiveUser(this.activeUsers));
+			reduxStore.dispatch(setActiveUser(this.activeUsers));
 			this._setUserTimer = null;
 			return this.activeUsers = {};
-		}, 5000);
-		this.activeUsers[ddpMessage.id] = status;
+		}, 3000);
+		this.activeUsers[ddpMessage.id] = ddpMessage.fields;
 	},
 	reconnect() {
 		if (this.ddp) {
@@ -124,6 +125,8 @@ const RocketChat = {
 				RocketChat.getSettings();
 				RocketChat.getPermissions();
 				RocketChat.getCustomEmoji();
+				this.ddp.subscribe('activeUsers');
+				this.ddp.subscribe('roles');
 			});
 
 			this.ddp.on('error', (err) => {
@@ -131,8 +134,6 @@ const RocketChat = {
 				reduxStore.dispatch(connectFailure());
 			});
 
-			this.ddp.on('connected', () => this.ddp.subscribe('activeUsers', null, false));
-
 			this.ddp.on('users', ddpMessage => RocketChat._setUser(ddpMessage));
 
 			this.ddp.on('stream-room-messages', (ddpMessage) => {
@@ -171,6 +172,12 @@ const RocketChat = {
 						sub.roomUpdatedAt = data._updatedAt;
 						sub.lastMessage = normalizeMessage(data.lastMessage);
 						sub.ro = data.ro;
+						sub.description = data.description;
+						sub.topic = data.topic;
+						sub.announcement = data.announcement;
+						sub.reactWhenReadOnly = data.reactWhenReadOnly;
+						sub.archived = data.archived;
+						sub.joinCodeRequired = data.joinCodeRequired;
 					});
 				}
 			});
@@ -334,6 +341,28 @@ const RocketChat = {
 					this.loginServiceTimer = setTimeout(() => reduxStore.dispatch(removeLoginServices()), 1000);
 				}
 			});
+
+			this.ddp.on('rocketchat_roles', (ddpMessage) => {
+				this.roles = this.roles || {};
+
+				if (this.roleTimer) {
+					clearTimeout(this.roleTimer);
+					this.roleTimer = null;
+				}
+				this.roleTimer = setTimeout(() => {
+					reduxStore.dispatch(setRoles(this.roles));
+
+					database.write(() => {
+						_.forEach(this.roles, (description, _id) => {
+							database.create('roles', { _id, description }, true);
+						});
+					});
+
+					this.roleTimer = null;
+					return this.roles = {};
+				}, 5000);
+				this.roles[ddpMessage.id] = ddpMessage.fields.description;
+			});
 		}).catch(console.log);
 	},
 
@@ -649,6 +678,9 @@ const RocketChat = {
 				subscription.description = room.description;
 				subscription.topic = room.topic;
 				subscription.announcement = room.announcement;
+				subscription.reactWhenReadOnly = room.reactWhenReadOnly;
+				subscription.archived = room.archived;
+				subscription.joinCodeRequired = room.joinCodeRequired;
 			}
 			if (subscription.roles) {
 				subscription.roles = subscription.roles.map(role => ({ value: role }));
@@ -823,6 +855,17 @@ const RocketChat = {
 	getRoomMembers(rid, allUsers) {
 		return call('getUsersOfRoom', rid, allUsers);
 	},
+	getUserRoles() {
+		return call('getUserRoles');
+	},
+	async getRoomMember(rid, currentUserId) {
+		try {
+			const membersResult = await RocketChat.getRoomMembers(rid, true);
+			return Promise.resolve(membersResult.records.find(m => m.id !== currentUserId));
+		} catch (error) {
+			return Promise.reject(error);
+		}
+	},
 	toggleBlockUser(rid, blocked, block) {
 		if (block) {
 			return call('blockUser', { rid, blocked });
@@ -831,6 +874,40 @@ const RocketChat = {
 	},
 	leaveRoom(rid) {
 		return call('leaveRoom', rid);
+	},
+	eraseRoom(rid) {
+		return call('eraseRoom', rid);
+	},
+	toggleArchiveRoom(rid, archive) {
+		if (archive) {
+			return call('archiveRoom', rid);
+		}
+		return call('unarchiveRoom', rid);
+	},
+	saveRoomSettings(rid, params) {
+		return call('saveRoomSettings', rid, params);
+	},
+	hasPermission(permissions, rid) {
+		// get the room from realm
+		const room = database.objects('subscriptions').filtered('rid = $0', rid)[0];
+		// get room roles
+		const { roles } = room;
+		// transform room roles to array
+		const roomRoles = Array.from(Object.keys(roles), i => roles[i].value);
+		// get user roles on the server from redux
+		const userRoles = reduxStore.getState().login.user.roles || [];
+		// get all permissions from redux
+		const allPermissions = reduxStore.getState().permissions;
+		// merge both roles
+		const mergedRoles = [...new Set([...roomRoles, ...userRoles])];
+
+		// return permissions in object format
+		// e.g. { 'edit-room': true, 'set-readonly': false }
+		return permissions.reduce((result, permission) => {
+			result[permission] = returnAnArray(allPermissions[permission])
+				.some(item => mergedRoles.indexOf(item) !== -1);
+			return result;
+		}, {});
 	}
 };
 
diff --git a/app/reducers/index.js b/app/reducers/index.js
index e7603c54c..3a4c0dac1 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -12,6 +12,7 @@ import app from './app';
 import permissions from './permissions';
 import customEmojis from './customEmojis';
 import activeUsers from './activeUsers';
+import roles from './roles';
 import starredMessages from './starredMessages';
 import pinnedMessages from './pinnedMessages';
 import mentionedMessages from './mentionedMessages';
@@ -32,6 +33,7 @@ export default combineReducers({
 	permissions,
 	customEmojis,
 	activeUsers,
+	roles,
 	starredMessages,
 	pinnedMessages,
 	mentionedMessages,
diff --git a/app/reducers/roles.js b/app/reducers/roles.js
new file mode 100644
index 000000000..f0e964e6c
--- /dev/null
+++ b/app/reducers/roles.js
@@ -0,0 +1,15 @@
+import * as types from '../actions/actionsTypes';
+
+const initialState = {};
+
+export default (state = initialState, action) => {
+	switch (action.type) {
+		case types.ROLES.SET:
+			return {
+				...state,
+				...action.data
+			};
+		default:
+			return state;
+	}
+};
diff --git a/app/sagas/activeUsers.js b/app/sagas/activeUsers.js
deleted file mode 100644
index 70ca903ef..000000000
--- a/app/sagas/activeUsers.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { put, takeLatest } from 'redux-saga/effects';
-import * as types from '../actions/actionsTypes';
-
-import { setActiveUser } from '../actions/activeUsers';
-
-const watchActiveUsers = function* handleInput({ users }) {
-	yield put(setActiveUser(users));
-};
-
-const root = function* root() {
-	yield takeLatest(types.ACTIVE_USERS.REQUEST, watchActiveUsers);
-};
-export default root;
diff --git a/app/sagas/index.js b/app/sagas/index.js
index 39aa6836e..ab82fdefc 100644
--- a/app/sagas/index.js
+++ b/app/sagas/index.js
@@ -8,7 +8,6 @@ import selectServer from './selectServer';
 import createChannel from './createChannel';
 import init from './init';
 import state from './state';
-import activeUsers from './activeUsers';
 import starredMessages from './starredMessages';
 import pinnedMessages from './pinnedMessages';
 import mentionedMessages from './mentionedMessages';
@@ -26,7 +25,6 @@ const root = function* root() {
 		messages(),
 		selectServer(),
 		state(),
-		activeUsers(),
 		starredMessages(),
 		pinnedMessages(),
 		mentionedMessages(),
diff --git a/app/sagas/init.js b/app/sagas/init.js
index 152991124..423e91a96 100644
--- a/app/sagas/init.js
+++ b/app/sagas/init.js
@@ -4,6 +4,7 @@ import * as actions from '../actions';
 import { setServer } from '../actions/server';
 import { restoreToken } from '../actions/login';
 import { APP } from '../actions/actionsTypes';
+import { setRoles } from '../actions/roles';
 import database from '../lib/realm';
 import RocketChat from '../lib/rocketchat';
 
@@ -23,6 +24,11 @@ const restore = function* restore() {
 			yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
 			const emojis = database.objects('customEmojis');
 			yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length))));
+			const roles = database.objects('roles');
+			yield put(setRoles(roles.reduce((result, role) => {
+				result[role._id] = role.description;
+				return result;
+			}, {})));
 		}
 		yield put(actions.appReady({}));
 	} catch (e) {
diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js
index a3017655c..ded0d4e63 100644
--- a/app/sagas/rooms.js
+++ b/app/sagas/rooms.js
@@ -11,6 +11,7 @@ import database from '../lib/realm';
 import * as NavigationService from '../containers/routes/NavigationService';
 
 const leaveRoom = rid => RocketChat.leaveRoom(rid);
+const eraseRoom = rid => RocketChat.eraseRoom(rid);
 
 const getRooms = function* getRooms() {
 	return yield RocketChat.getRooms();
@@ -121,26 +122,39 @@ const updateLastOpen = function* updateLastOpen() {
 	yield put(setLastOpen());
 };
 
+const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) {
+	NavigationService.goRoomsList();
+	yield delay(1000);
+	database.write(() => {
+		const messages = database.objects('messages').filtered('rid = $0', rid);
+		database.delete(messages);
+		const subscription = database.objects('subscriptions').filtered('rid = $0', rid);
+		database.delete(subscription);
+	});
+};
+
 const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
 	try {
 		yield call(leaveRoom, rid);
-		NavigationService.goRoomsList();
-		yield delay(1000);
-		database.write(() => {
-			const messages = database.objects('messages').filtered('rid = $0', rid);
-			database.delete(messages);
-			const subscription = database.objects('subscriptions').filtered('rid = $0', rid);
-			database.delete(subscription);
-		});
+		yield goRoomsListAndDelete(rid);
 	} catch (e) {
 		if (e.error === 'error-you-are-last-owner') {
 			Alert.alert('You are the last owner. Please set new owner before leaving the room.');
 		} else {
-			Alert.alert(e);
+			Alert.alert('Something happened when leaving room!');
 		}
 	}
 };
 
+const handleEraseRoom = function* handleEraseRoom({ rid }) {
+	try {
+		yield call(eraseRoom, rid);
+		yield goRoomsListAndDelete(rid);
+	} catch (e) {
+		Alert.alert('Something happened when erasing room!');
+	}
+};
+
 const root = function* root() {
 	yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
 	yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest);
@@ -150,5 +164,6 @@ const root = function* root() {
 	yield takeLatest(FOREGROUND, watchRoomsRequest);
 	yield takeLatest(BACKGROUND, updateLastOpen);
 	yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
+	yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
 };
 export default root;
diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js
index 7eb6ea585..0b2632e40 100644
--- a/app/views/RoomActionsView/index.js
+++ b/app/views/RoomActionsView/index.js
@@ -6,7 +6,9 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
 import { connect } from 'react-redux';
 
 import styles from './styles';
+import sharedStyles from '../Styles';
 import Avatar from '../../containers/Avatar';
+import Status from '../../containers/status';
 import Touch from '../../utils/touch';
 import database from '../../lib/realm';
 import RocketChat from '../../lib/rocketchat';
@@ -33,13 +35,15 @@ export default class RoomActionsView extends React.PureComponent {
 		this.state = {
 			sections: [],
 			room: {},
-			members: []
+			members: [],
+			member: {}
 		};
 	}
 
 	async componentDidMount() {
 		await this.updateRoom();
 		this.updateRoomMembers();
+		this.updateRoomMember();
 		this.rooms.addListener(this.updateRoom);
 	}
 
@@ -59,7 +63,7 @@ export default class RoomActionsView extends React.PureComponent {
 	getRoomTitle = room => (room.t === 'd' ? room.fname : room.name);
 
 	updateRoomMembers = async() => {
-		let members;
+		let members = [];
 		try {
 			const membersResult = await RocketChat.getRoomMembers(this.state.room.rid, false);
 			members = membersResult.records;
@@ -70,6 +74,17 @@ export default class RoomActionsView extends React.PureComponent {
 		this.updateSections();
 	}
 
+	updateRoomMember = async() => {
+		if (this.state.room.t === 'd') {
+			try {
+				const member = await RocketChat.getRoomMember(this.state.room.rid, this.props.user.id);
+				this.setState({ member });
+			} catch (error) {
+				console.warn(error);
+			}
+		}
+	}
+
 	updateRoom = async() => {
 		const [room] = this.rooms;
 		await this.setState({ room });
@@ -80,12 +95,17 @@ export default class RoomActionsView extends React.PureComponent {
 		const { rid, t, blocked } = this.state.room;
 		const { members } = this.state;
 		const sections = [{
-			data: [{ icon: 'ios-star', name: 'USER' }],
+			data: [{
+				icon: 'ios-star',
+				name: 'USER',
+				route: 'RoomInfo',
+				params: { rid }
+			}],
 			renderItem: this.renderRoomInfo
 		}, {
 			data: [
-				{ icon: 'ios-call-outline', name: 'Voice call' },
-				{ icon: 'ios-videocam-outline', name: 'Video call' }
+				{ icon: 'ios-call-outline', name: 'Voice call', disabled: true },
+				{ icon: 'ios-videocam-outline', name: 'Video call', disabled: true }
 			],
 			renderItem: this.renderItem
 		}, {
@@ -108,8 +128,8 @@ export default class RoomActionsView extends React.PureComponent {
 					route: 'StarredMessages',
 					params: { rid }
 				},
-				{ icon: 'ios-search', name: 'Search' },
-				{ icon: 'ios-share-outline', name: 'Share' },
+				{ icon: 'ios-search', name: 'Search', disabled: true },
+				{ icon: 'ios-share-outline', name: 'Share', disabled: true },
 				{
 					icon: 'ios-pin',
 					name: 'Pinned',
@@ -122,14 +142,14 @@ export default class RoomActionsView extends React.PureComponent {
 					route: 'SnippetedMessages',
 					params: { rid }
 				},
-				{ icon: 'ios-notifications-outline', name: 'Notifications preferences' }
+				{ icon: 'ios-notifications-outline', name: 'Notifications preferences', disabled: true }
 			],
 			renderItem: this.renderItem
 		}];
 		if (t === 'd') {
 			sections.push({
 				data: [
-					{ icon: 'ios-volume-off', name: 'Mute user' },
+					{ icon: 'ios-volume-off', name: 'Mute user', disabled: true },
 					{
 						icon: 'block',
 						name: `${ blocked ? 'Unblock' : 'Block' } user`,
@@ -151,7 +171,7 @@ export default class RoomActionsView extends React.PureComponent {
 			}
 			sections.push({
 				data: [
-					{ icon: 'ios-volume-off', name: 'Mute channel' },
+					{ icon: 'ios-volume-off', name: 'Mute channel', disabled: true },
 					{
 						icon: 'block',
 						name: 'Leave channel',
@@ -167,8 +187,7 @@ export default class RoomActionsView extends React.PureComponent {
 
 	toggleBlockUser = () => {
 		const { rid, blocked } = this.state.room;
-		const { members } = this.state;
-		const member = members.find(m => m.id !== this.props.user.id);
+		const { member } = this.state;
 		RocketChat.toggleBlockUser(rid, member._id, !blocked);
 	}
 
@@ -185,16 +204,14 @@ export default class RoomActionsView extends React.PureComponent {
 				{
 					text: 'Yes, leave it!',
 					style: 'destructive',
-					onPress: async() => {
-						this.props.leaveRoom(room.rid);
-					}
+					onPress: () => this.props.leaveRoom(room.rid)
 				}
 			]
 		);
 	}
 
 	renderRoomInfo = ({ item }) => {
-		const { room } = this.state;
+		const { room, member } = this.state;
 		const { name, t, topic } = room;
 		return (
 			this.renderTouchableItem([
@@ -205,12 +222,14 @@ export default class RoomActionsView extends React.PureComponent {
 					style={styles.avatar}
 					baseUrl={this.props.baseUrl}
 					type={t}
-				/>,
+				>
+					{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
+				</Avatar>,
 				<View key='name' style={styles.roomTitleContainer}>
 					<Text style={styles.roomTitle}>{ this.getRoomTitle(room) }</Text>
 					<Text style={styles.roomDescription} ellipsizeMode='tail' numberOfLines={1}>{t === 'd' ? `@${ name }` : topic}</Text>
 				</View>,
-				<Icon key='icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#cbced1' />
+				<Icon key='icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#ccc' />
 			], item)
 		);
 	}
@@ -223,7 +242,7 @@ export default class RoomActionsView extends React.PureComponent {
 			accessibilityLabel={item.name}
 			accessibilityTraits='button'
 		>
-			<View style={styles.sectionItem}>
+			<View style={[styles.sectionItem, item.disabled && styles.sectionItemDisabled]}>
 				{subview}
 			</View>
 		</Touch>
@@ -241,11 +260,13 @@ export default class RoomActionsView extends React.PureComponent {
 			<Icon key='left-icon' name={item.icon} size={24} style={styles.sectionItemIcon} />,
 			<Text key='name' style={styles.sectionItemName}>{ item.name }</Text>,
 			item.description && <Text key='description' style={styles.sectionItemDescription}>{ item.description }</Text>,
-			<Icon key='right-icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#cbced1' />
+			<Icon key='right-icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#ccc' />
 		];
 		return this.renderTouchableItem(subview, item);
 	}
 
+	renderSeparator = () => <View style={styles.separator} />;
+
 	renderSectionSeparator = (data) => {
 		if (!data.trailingItem) {
 			if (!data.trailingSection) {
@@ -265,6 +286,7 @@ export default class RoomActionsView extends React.PureComponent {
 				stickySectionHeadersEnabled={false}
 				sections={this.state.sections}
 				SectionSeparatorComponent={this.renderSectionSeparator}
+				ItemSeparatorComponent={this.renderSeparator}
 				keyExtractor={(item, index) => index}
 			/>
 		);
diff --git a/app/views/RoomActionsView/styles.js b/app/views/RoomActionsView/styles.js
index dba0c3b93..175c04bd3 100644
--- a/app/views/RoomActionsView/styles.js
+++ b/app/views/RoomActionsView/styles.js
@@ -13,10 +13,13 @@ export default StyleSheet.create({
 	},
 	sectionItem: {
 		backgroundColor: '#ffffff',
-		paddingVertical: 10,
+		paddingVertical: 16,
 		flexDirection: 'row',
 		alignItems: 'center'
 	},
+	sectionItemDisabled: {
+		opacity: 0.3
+	},
 	sectionItemIcon: {
 		width: 45,
 		textAlign: 'center'
@@ -25,7 +28,11 @@ export default StyleSheet.create({
 		flex: 1
 	},
 	sectionItemDescription: {
-		color: '#cbced1'
+		color: '#ccc'
+	},
+	separator: {
+		height: StyleSheet.hairlineWidth,
+		backgroundColor: '#ddd'
 	},
 	sectionSeparator: {
 		height: 10,
@@ -49,6 +56,6 @@ export default StyleSheet.create({
 	},
 	roomDescription: {
 		fontSize: 12,
-		color: '#cbced1'
+		color: '#ccc'
 	}
 });
diff --git a/app/views/RoomInfoEditView/SwitchContainer.js b/app/views/RoomInfoEditView/SwitchContainer.js
new file mode 100644
index 000000000..7cb85b8af
--- /dev/null
+++ b/app/views/RoomInfoEditView/SwitchContainer.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import { View, Text, Switch } from 'react-native';
+import PropTypes from 'prop-types';
+
+import styles from './styles';
+import sharedStyles from '../../views/Styles';
+
+export default class SwitchContainer extends React.PureComponent {
+	static propTypes = {
+		value: PropTypes.bool,
+		disabled: PropTypes.bool,
+		leftLabelPrimary: PropTypes.string,
+		leftLabelSecondary: PropTypes.string,
+		rightLabelPrimary: PropTypes.string,
+		rightLabelSecondary: PropTypes.string,
+		onValueChange: PropTypes.func
+	}
+
+	render() {
+		const {
+			value, disabled, onValueChange, leftLabelPrimary, leftLabelSecondary, rightLabelPrimary, rightLabelSecondary
+		} = this.props;
+		return (
+			[
+				<View key='switch-container' style={styles.switchContainer}>
+					<View style={[styles.switchLabelContainer, sharedStyles.alignItemsFlexEnd]}>
+						<Text style={styles.switchLabelPrimary}>{leftLabelPrimary}</Text>
+						<Text style={[styles.switchLabelSecondary, sharedStyles.textAlignRight]}>{leftLabelSecondary}</Text>
+					</View>
+					<Switch
+						style={styles.switch}
+						onValueChange={onValueChange}
+						value={value}
+						disabled={disabled}
+					/>
+					<View style={styles.switchLabelContainer}>
+						<Text style={styles.switchLabelPrimary}>{rightLabelPrimary}</Text>
+						<Text style={styles.switchLabelSecondary}>{rightLabelSecondary}</Text>
+					</View>
+				</View>,
+				<View key='switch-divider' style={styles.divider} />
+			]
+		);
+	}
+}
diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.js
new file mode 100644
index 000000000..3b8834f06
--- /dev/null
+++ b/app/views/RoomInfoEditView/index.js
@@ -0,0 +1,381 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Text, View, ScrollView, TouchableOpacity, SafeAreaView, Keyboard, Alert } from 'react-native';
+import Spinner from 'react-native-loading-spinner-overlay';
+import { connect } from 'react-redux';
+
+import KeyboardView from '../../presentation/KeyboardView';
+import sharedStyles from '../Styles';
+import styles from './styles';
+import scrollPersistTaps from '../../utils/scrollPersistTaps';
+import { showErrorAlert, showToast } from '../../utils/info';
+import database from '../../lib/realm';
+import RocketChat from '../../lib/rocketchat';
+import { eraseRoom } from '../../actions/room';
+import RCTextInput from '../../containers/TextInput';
+import SwitchContainer from './SwitchContainer';
+import random from '../../utils/random';
+
+const PERMISSION_SET_READONLY = 'set-readonly';
+const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
+const PERMISSION_ARCHIVE = 'archive-room';
+const PERMISSION_UNARCHIVE = 'unarchive-room';
+const PERMISSION_DELETE_C = 'delete-c';
+const PERMISSION_DELETE_P = 'delete-p';
+const PERMISSIONS_ARRAY = [
+	PERMISSION_SET_READONLY,
+	PERMISSION_SET_REACT_WHEN_READONLY,
+	PERMISSION_ARCHIVE,
+	PERMISSION_UNARCHIVE,
+	PERMISSION_DELETE_C,
+	PERMISSION_DELETE_P
+];
+
+@connect(null, dispatch => ({
+	eraseRoom: rid => dispatch(eraseRoom(rid))
+}))
+export default class RoomInfoEditView extends React.Component {
+	static propTypes = {
+		navigation: PropTypes.object,
+		eraseRoom: PropTypes.func
+	};
+
+	constructor(props) {
+		super(props);
+		const { rid } = props.navigation.state.params;
+		this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
+		this.permissions = {};
+		this.state = {
+			room: {},
+			name: '',
+			description: '',
+			topic: '',
+			announcement: '',
+			joinCode: '',
+			nameError: {},
+			saving: false,
+			t: false,
+			ro: false,
+			reactWhenReadOnly: false
+		};
+	}
+
+	async componentDidMount() {
+		await this.updateRoom();
+		this.init();
+		this.rooms.addListener(this.updateRoom);
+		this.permissions = RocketChat.hasPermission(PERMISSIONS_ARRAY, this.state.room.rid);
+	}
+
+	componentWillUnmount() {
+		this.rooms.removeAllListeners();
+	}
+
+	updateRoom = async() => {
+		const [room] = this.rooms;
+		this.setState({ room });
+	}
+
+	init = () => {
+		const {
+			name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired
+		} = this.state.room;
+		// fake password just to user knows about it
+		this.randomValue = random(15);
+		this.setState({
+			name,
+			description,
+			topic,
+			announcement,
+			t: t === 'p',
+			ro,
+			reactWhenReadOnly,
+			joinCode: joinCodeRequired ? this.randomValue : ''
+		});
+	}
+
+	clearErrors = () => {
+		this.setState({
+			nameError: {}
+		});
+	}
+
+	reset = () => {
+		this.clearErrors();
+		this.init();
+	}
+
+	formIsChanged = () => {
+		const {
+			room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode
+		} = this.state;
+		return !(room.name === name &&
+			room.description === description &&
+			room.topic === topic &&
+			room.announcement === announcement &&
+			this.randomValue === joinCode &&
+			room.t === 'p' === t &&
+			room.ro === ro &&
+			room.reactWhenReadOnly === reactWhenReadOnly
+		);
+	}
+
+	submit = async() => {
+		Keyboard.dismiss();
+		const {
+			room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode
+		} = this.state;
+
+		this.setState({ saving: true });
+		let error = false;
+
+		if (!this.formIsChanged()) {
+			showErrorAlert('Nothing to save!');
+			return;
+		}
+
+		// Clear error objects
+		await this.clearErrors();
+
+		const params = {};
+
+		// Name
+		if (room.name !== name) {
+			params.roomName = name;
+		}
+		// Description
+		if (room.description !== description) {
+			params.roomDescription = description;
+		}
+		// Topic
+		if (room.topic !== topic) {
+			params.roomTopic = topic;
+		}
+		// Announcement
+		if (room.announcement !== announcement) {
+			params.roomAnnouncement = announcement;
+		}
+		// Room Type
+		if (room.t !== t) {
+			params.roomType = t ? 'p' : 'c';
+		}
+		// Read Only
+		if (room.ro !== ro) {
+			params.readOnly = ro;
+		}
+		// React When Read Only
+		if (room.reactWhenReadOnly !== reactWhenReadOnly) {
+			params.reactWhenReadOnly = reactWhenReadOnly;
+		}
+
+		// Join Code
+		if (this.randomValue !== joinCode) {
+			params.joinCode = joinCode;
+		}
+
+		try {
+			await RocketChat.saveRoomSettings(room.rid, params);
+		} catch (e) {
+			if (e.error === 'error-invalid-room-name') {
+				this.setState({ nameError: e });
+			}
+			error = true;
+		}
+
+		await this.setState({ saving: false });
+		setTimeout(() => {
+			if (error) {
+				showErrorAlert('There was an error while saving settings!');
+			} else {
+				showToast('Settings succesfully changed!');
+			}
+		}, 100);
+	}
+
+	delete = () => {
+		Alert.alert(
+			'Are you sure?',
+			'Deleting a room will delete all messages posted within the room. This cannot be undone.',
+			[
+				{
+					text: 'Cancel',
+					style: 'cancel'
+				},
+				{
+					text: 'Yes, delete it!',
+					style: 'destructive',
+					onPress: () => this.props.eraseRoom(this.state.room.rid)
+				}
+			],
+			{ cancelable: false }
+		);
+	}
+
+	toggleArchive = () => {
+		const { archived } = this.state.room;
+		const action = `${ archived ? 'un' : '' }archive`;
+		Alert.alert(
+			'Are you sure?',
+			`Do you really want to ${ action } this room?`,
+			[
+				{
+					text: 'Cancel',
+					style: 'cancel'
+				},
+				{
+					text: `Yes, ${ action } it!`,
+					style: 'destructive',
+					onPress: () => {
+						try {
+							RocketChat.toggleArchiveRoom(this.state.room.rid, !archived);
+						} catch (error) {
+							alert(error);
+						}
+					}
+				}
+			],
+			{ cancelable: false }
+		);
+	}
+
+	hasDeletePermission = () => (
+		this.state.room.t === 'p' ? this.permissions[PERMISSION_DELETE_P] : this.permissions[PERMISSION_DELETE_C]
+	);
+
+	hasArchivePermission = () => (
+		this.permissions[PERMISSION_ARCHIVE] || this.permissions[PERMISSION_UNARCHIVE]
+	);
+
+	render() {
+		const {
+			name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode
+		} = this.state;
+		return (
+			<KeyboardView
+				contentContainerStyle={sharedStyles.container}
+				keyboardVerticalOffset={128}
+			>
+				<ScrollView
+					style={sharedStyles.loginView}
+					{...scrollPersistTaps}
+				>
+					<SafeAreaView>
+						<View style={sharedStyles.formContainer}>
+							<RCTextInput
+								inputRef={(e) => { this.name = e; }}
+								label='Name'
+								value={name}
+								onChangeText={value => this.setState({ name: value })}
+								onSubmitEditing={() => { this.description.focus(); }}
+								error={nameError}
+							/>
+							<RCTextInput
+								inputRef={(e) => { this.description = e; }}
+								label='Description'
+								value={description}
+								onChangeText={value => this.setState({ description: value })}
+								onSubmitEditing={() => { this.topic.focus(); }}
+								inputProps={{ multiline: true }}
+							/>
+							<RCTextInput
+								inputRef={(e) => { this.topic = e; }}
+								label='Topic'
+								value={topic}
+								onChangeText={value => this.setState({ topic: value })}
+								onSubmitEditing={() => { this.announcement.focus(); }}
+								inputProps={{ multiline: true }}
+							/>
+							<RCTextInput
+								inputRef={(e) => { this.announcement = e; }}
+								label='Announcement'
+								value={announcement}
+								onChangeText={value => this.setState({ announcement: value })}
+								onSubmitEditing={() => { this.joinCode.focus(); }}
+								inputProps={{ multiline: true }}
+							/>
+							<RCTextInput
+								inputRef={(e) => { this.joinCode = e; }}
+								label='Password'
+								value={joinCode}
+								onChangeText={value => this.setState({ joinCode: value })}
+								onSubmitEditing={this.submit}
+								inputProps={{ secureTextEntry: true }}
+							/>
+							<SwitchContainer
+								value={t}
+								leftLabelPrimary='Public'
+								leftLabelSecondary='Everyone can access this channel'
+								rightLabelPrimary='Private'
+								rightLabelSecondary='Just invited people can access this channel'
+								onValueChange={value => this.setState({ t: value })}
+							/>
+							<SwitchContainer
+								value={ro}
+								leftLabelPrimary='Colaborative'
+								leftLabelSecondary='All users in the channel can write new messages'
+								rightLabelPrimary='Read Only'
+								rightLabelSecondary='Only authorized users can write new messages'
+								onValueChange={value => this.setState({ ro: value })}
+								disabled={!this.permissions[PERMISSION_SET_READONLY]}
+							/>
+							{ro &&
+								<SwitchContainer
+									value={reactWhenReadOnly}
+									leftLabelPrimary='No Reactions'
+									leftLabelSecondary='Reactions are disabled'
+									rightLabelPrimary='Allow Reactions'
+									rightLabelSecondary='Reactions are enabled'
+									onValueChange={value => this.setState({ reactWhenReadOnly: value })}
+									disabled={!this.permissions[PERMISSION_SET_REACT_WHEN_READONLY]}
+								/>
+							}
+							<TouchableOpacity
+								style={[sharedStyles.buttonContainer, !this.formIsChanged() && styles.buttonContainerDisabled]}
+								onPress={this.submit}
+								disabled={!this.formIsChanged()}
+							>
+								<Text style={sharedStyles.button} accessibilityTraits='button'>SAVE</Text>
+							</TouchableOpacity>
+							<View style={{ flexDirection: 'row' }}>
+								<TouchableOpacity
+									style={[sharedStyles.buttonContainer_inverted, styles.buttonInverted, { flex: 1 }]}
+									onPress={this.reset}
+								>
+									<Text style={sharedStyles.button_inverted} accessibilityTraits='button'>RESET</Text>
+								</TouchableOpacity>
+								<TouchableOpacity
+									style={[
+										sharedStyles.buttonContainer_inverted,
+										styles.buttonDanger,
+										!this.hasArchivePermission() && sharedStyles.opacity5,
+										{ flex: 1, marginLeft: 10 }
+									]}
+									onPress={this.toggleArchive}
+									disabled={!this.hasArchivePermission()}
+								>
+									<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>
+										{ room.archived ? 'UNARCHIVE' : 'ARCHIVE' }
+									</Text>
+								</TouchableOpacity>
+							</View>
+							<View style={styles.divider} />
+							<TouchableOpacity
+								style={[
+									sharedStyles.buttonContainer_inverted,
+									sharedStyles.buttonContainerLastChild,
+									styles.buttonDanger,
+									!this.hasDeletePermission() && sharedStyles.opacity5
+								]}
+								onPress={this.delete}
+								disabled={!this.hasDeletePermission()}
+							>
+								<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>DELETE</Text>
+							</TouchableOpacity>
+						</View>
+						<Spinner visible={this.state.saving} textContent='Loading...' textStyle={{ color: '#FFF' }} />
+					</SafeAreaView>
+				</ScrollView>
+			</KeyboardView>
+		);
+	}
+}
diff --git a/app/views/RoomInfoEditView/styles.js b/app/views/RoomInfoEditView/styles.js
new file mode 100644
index 000000000..e98f6786c
--- /dev/null
+++ b/app/views/RoomInfoEditView/styles.js
@@ -0,0 +1,46 @@
+import { StyleSheet } from 'react-native';
+
+import { COLOR_DANGER } from '../../constants/colors';
+
+export default StyleSheet.create({
+	buttonInverted: {
+		borderColor: 'rgba(0,0,0,.15)',
+		borderWidth: 2,
+		borderRadius: 2
+	},
+	buttonContainerDisabled: {
+		backgroundColor: 'rgba(65, 72, 82, 0.7)'
+	},
+	buttonDanger: {
+		borderColor: COLOR_DANGER,
+		borderWidth: 2,
+		borderRadius: 2
+	},
+	colorDanger: {
+		color: COLOR_DANGER
+	},
+	switchContainer: {
+		flexDirection: 'row',
+		alignItems: 'flex-start'
+	},
+	switchLabelContainer: {
+		flex: 1,
+		paddingHorizontal: 10
+	},
+	switchLabelPrimary: {
+		fontSize: 16,
+		paddingBottom: 6
+	},
+	switchLabelSecondary: {
+		fontSize: 12
+	},
+	switch: {
+		alignSelf: 'center'
+	},
+	divider: {
+		height: StyleSheet.hairlineWidth,
+		borderColor: '#ddd',
+		borderBottomWidth: StyleSheet.hairlineWidth,
+		marginVertical: 20
+	}
+});
diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js
new file mode 100644
index 000000000..bbcf14b5e
--- /dev/null
+++ b/app/views/RoomInfoView/index.js
@@ -0,0 +1,192 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { View, Text, ScrollView } from 'react-native';
+import { connect } from 'react-redux';
+import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
+import moment from 'moment';
+
+import Status from '../../containers/status';
+import Avatar from '../../containers/Avatar';
+import styles from './styles';
+import sharedStyles from '../Styles';
+import database from '../../lib/realm';
+import RocketChat from '../../lib/rocketchat';
+import Touch from '../../utils/touch';
+
+const PERMISSION_EDIT_ROOM = 'edit-room';
+
+const camelize = str => str.replace(/^(.)/, (match, chr) => chr.toUpperCase());
+
+@connect(state => ({
+	baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
+	user: state.login.user,
+	permissions: state.permissions,
+	activeUsers: state.activeUsers,
+	Message_TimeFormat: state.settings.Message_TimeFormat,
+	roles: state.roles
+}))
+export default class RoomInfoView extends React.Component {
+	static propTypes = {
+		baseUrl: PropTypes.string,
+		user: PropTypes.object,
+		navigation: PropTypes.object,
+		activeUsers: PropTypes.object,
+		Message_TimeFormat: PropTypes.string,
+		roles: PropTypes.object
+	}
+
+	static navigationOptions = ({ navigation }) => {
+		const params = navigation.state.params || {};
+		if (!params.hasEditPermission) {
+			return;
+		}
+		return {
+			headerRight: (
+				<Touch
+					onPress={() => navigation.navigate('RoomInfoEdit', { rid: navigation.state.params.rid })}
+					underlayColor='#ffffff'
+					activeOpacity={0.5}
+					accessibilityLabel='edit'
+					accessibilityTraits='button'
+				>
+					<View style={styles.headerButton}>
+						<MaterialIcon name='edit' size={20} />
+					</View>
+				</Touch>
+			)
+		};
+	};
+
+	constructor(props) {
+		super(props);
+		const { rid } = props.navigation.state.params;
+		this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
+		this.sub = {
+			unsubscribe: () => {}
+		};
+		this.state = {
+			room: {},
+			roomUser: {},
+			roles: []
+		};
+	}
+
+	async componentDidMount() {
+		await this.updateRoom();
+		this.rooms.addListener(this.updateRoom);
+
+		// get user of room
+		if (this.state.room.t === 'd') {
+			try {
+				const roomUser = await RocketChat.getRoomMember(this.state.room.rid, this.props.user.id);
+				this.setState({ roomUser });
+				const username = this.state.room.name;
+
+				const activeUser = this.props.activeUsers[roomUser._id];
+				if (!activeUser || !activeUser.utcOffset) {
+					// get full user data looking for utcOffset
+					// will be catched by .on('users) and saved on activeUsers reducer
+					this.getFullUserData(username);
+				}
+
+				// get all users roles
+				// needs to be changed by a better method
+				const allUsersRoles = await RocketChat.getUserRoles();
+				const userRoles = allUsersRoles.find(user => user.username === username);
+				if (userRoles) {
+					this.setState({ roles: userRoles.roles || [] });
+				}
+			} catch (error) {
+				alert(error);
+			}
+		} else {
+			const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], this.state.room.rid);
+			this.props.navigation.setParams({ hasEditPermission: permissions[PERMISSION_EDIT_ROOM] });
+		}
+	}
+
+	componentWillUnmount() {
+		this.rooms.removeAllListeners();
+		this.sub.unsubscribe();
+	}
+
+	getFullUserData = async(username) => {
+		const result = await RocketChat.subscribe('fullUserData', username);
+		this.sub = result;
+	}
+
+	getRoomTitle = room => (room.t === 'd' ? room.fname : room.name);
+
+	isDirect = () => this.state.room.t === 'd';
+
+	updateRoom = async() => {
+		const [room] = this.rooms;
+		this.setState({ room });
+	}
+	// TODO: translate
+	renderItem = (key, room) => (
+		<View style={styles.item}>
+			<Text style={styles.itemLabel}>{camelize(key)}</Text>
+			<Text style={[styles.itemContent, !room[key] && styles.itemContent__empty]}>{ room[key] ? room[key] : `No ${ key } provided.` }</Text>
+		</View>
+	);
+
+	renderRoles = () => (
+		this.state.roles.length > 0 &&
+		<View style={styles.item}>
+			<Text style={styles.itemLabel}>Roles</Text>
+			<View style={styles.rolesContainer}>
+				{this.state.roles.map(role => (
+					<View style={styles.roleBadge} key={role}>
+						<Text>{ this.props.roles[role] }</Text>
+					</View>
+				))}
+			</View>
+		</View>
+	)
+
+	renderTimezone = (userId) => {
+		if (this.props.activeUsers[userId]) {
+			const { utcOffset } = this.props.activeUsers[userId];
+
+			if (!utcOffset) {
+				return null;
+			}
+			// TODO: translate
+			return (
+				<View style={styles.item}>
+					<Text style={styles.itemLabel}>Timezone</Text>
+					<Text style={styles.itemContent}>{moment().utcOffset(utcOffset).format(this.props.Message_TimeFormat)} (UTC { utcOffset })</Text>
+				</View>
+			);
+		}
+		return null;
+	}
+
+	render() {
+		const { room, roomUser } = this.state;
+		const { name, t } = room;
+		return (
+			<ScrollView style={styles.container}>
+				<View style={styles.avatarContainer}>
+					<Avatar
+						text={name}
+						size={100}
+						style={styles.avatar}
+						baseUrl={this.props.baseUrl}
+						type={t}
+					>
+						{t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
+					</Avatar>
+					<Text style={styles.roomTitle}>{ this.getRoomTitle(room) }</Text>
+				</View>
+
+				{!this.isDirect() && this.renderItem('description', room)}
+				{!this.isDirect() && this.renderItem('topic', room)}
+				{!this.isDirect() && this.renderItem('announcement', room)}
+				{this.isDirect() && this.renderRoles()}
+				{this.isDirect() && this.renderTimezone(roomUser._id)}
+			</ScrollView>
+		);
+	}
+}
diff --git a/app/views/RoomInfoView/styles.js b/app/views/RoomInfoView/styles.js
new file mode 100644
index 000000000..a0fc81839
--- /dev/null
+++ b/app/views/RoomInfoView/styles.js
@@ -0,0 +1,70 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+	container: {
+		flex: 1,
+		flexDirection: 'column',
+		backgroundColor: '#ffffff',
+		padding: 10
+	},
+	headerButton: {
+		backgroundColor: 'transparent',
+		height: 44,
+		width: 44,
+		alignItems: 'center',
+		justifyContent: 'center'
+	},
+	item: {
+		padding: 10,
+		// borderColor: '#EBEDF1',
+		// borderTopWidth: StyleSheet.hairlineWidth,
+		justifyContent: 'center'
+	},
+	avatarContainer: {
+		height: 250,
+		flexDirection: 'column',
+		alignItems: 'center',
+		justifyContent: 'center'
+	},
+	avatar: {
+		marginHorizontal: 10
+	},
+	roomTitle: {
+		fontSize: 18,
+		paddingTop: 20
+	},
+	roomDescription: {
+		fontSize: 14,
+		color: '#ccc',
+		paddingTop: 10
+	},
+	status: {
+		borderRadius: 24,
+		width: 24,
+		height: 24,
+		borderWidth: 4,
+		bottom: -4,
+		right: -4
+	},
+	itemLabel: {
+		fontWeight: '600',
+		marginBottom: 10
+	},
+	itemContent: {
+		color: '#ccc'
+	},
+	itemContent__empty: {
+		fontStyle: 'italic'
+	},
+	rolesContainer: {
+		flexDirection: 'row',
+		flexWrap: 'wrap'
+	},
+	roleBadge: {
+		padding: 8,
+		backgroundColor: '#ddd',
+		borderRadius: 2,
+		marginRight: 5,
+		marginBottom: 5
+	}
+});
diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js
index 321864f68..89beee3c1 100644
--- a/app/views/RoomMembersView/index.js
+++ b/app/views/RoomMembersView/index.js
@@ -4,6 +4,7 @@ import { FlatList, Text, View, TextInput } from 'react-native';
 import { connect } from 'react-redux';
 
 import styles from './styles';
+import sharedStyles from '../Styles';
 import Avatar from '../../containers/Avatar';
 import Status from '../../containers/status';
 import Touch from '../../utils/touch';
@@ -115,7 +116,7 @@ export default class MentionedMessagesView extends React.PureComponent {
 			accessibilityTraits='button'
 		>
 			<View style={styles.item}>
-				<Avatar text={item.username} size={30} type='d' style={styles.avatar}>{<Status style={styles.status} id={item._id} />}</Avatar>
+				<Avatar text={item.username} size={30} type='d' style={styles.avatar}>{<Status style={[sharedStyles.status, styles.status]} id={item._id} />}</Avatar>
 				<Text style={styles.username}>{item.username}</Text>
 			</View>
 		</Touch>
diff --git a/app/views/RoomMembersView/styles.js b/app/views/RoomMembersView/styles.js
index 68d6cc5ed..e624966e1 100644
--- a/app/views/RoomMembersView/styles.js
+++ b/app/views/RoomMembersView/styles.js
@@ -7,7 +7,7 @@ export default StyleSheet.create({
 	},
 	item: {
 		flexDirection: 'row',
-		paddingVertical: 8,
+		paddingVertical: 10,
 		paddingHorizontal: 16,
 		alignItems: 'center'
 	},
@@ -15,14 +15,12 @@ export default StyleSheet.create({
 		marginRight: 16
 	},
 	status: {
-		position: 'absolute',
-		bottom: -3,
-		right: -3,
+		bottom: -2,
+		right: -2,
 		borderWidth: 2,
-		borderColor: '#fff',
-		borderRadius: 12,
-		width: 12,
-		height: 12
+		borderRadius: 10,
+		width: 10,
+		height: 10
 	},
 	separator: {
 		height: StyleSheet.hairlineWidth,
diff --git a/app/views/RoomView/Header/index.js b/app/views/RoomView/Header/index.js
index 582fd72ca..811c6b913 100644
--- a/app/views/RoomView/Header/index.js
+++ b/app/views/RoomView/Header/index.js
@@ -50,7 +50,8 @@ export default class RoomHeaderView extends React.PureComponent {
 
 	getUserStatus() {
 		const userId = this.rid.replace(this.props.user.id, '').trim();
-		return this.props.activeUsers[userId] || 'offline';
+		const userInfo = this.props.activeUsers[userId];
+		return (userInfo && userInfo.status) || 'offline';
 	}
 
 	getUserStatusLabel() {
@@ -86,7 +87,12 @@ export default class RoomHeaderView extends React.PureComponent {
 		}
 
 		return (
-			<TouchableOpacity style={styles.titleContainer} accessibilityLabel={accessibilityLabel} accessibilityTraits='header'>
+			<TouchableOpacity
+				style={styles.titleContainer}
+				accessibilityLabel={accessibilityLabel}
+				accessibilityTraits='header'
+				onPress={() => this.props.navigation.navigate('RoomInfo', { rid: this.rid })}
+			>
 				{this.isDirect() ?
 					<View style={[styles.status, { backgroundColor: STATUS_COLORS[this.getUserStatus()] }]} />
 					: null
diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js
index ebe8c221a..ba1f4b9f5 100644
--- a/app/views/RoomView/index.js
+++ b/app/views/RoomView/index.js
@@ -157,6 +157,7 @@ export default class RoomView extends React.Component {
 			user={this.props.user}
 			onReactionPress={this.onReactionPress}
 			onLongPress={this.onMessageLongPress}
+			archived={this.state.room.archived}
 		/>
 	);
 
@@ -171,7 +172,7 @@ export default class RoomView extends React.Component {
 				</View>
 			);
 		}
-		if (this.state.room.ro) {
+		if (this.state.room.ro || this.state.room.archived) {
 			return (
 				<View style={styles.readOnly}>
 					<Text>This room is read only</Text>
diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js
index 1ad2a9e39..a38d7168b 100644
--- a/app/views/RoomsListView/index.js
+++ b/app/views/RoomsListView/index.js
@@ -29,7 +29,6 @@ const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
 	login: () => dispatch(actions.login()),
 	connect: () => dispatch(server.connectRequest())
 }))
-
 export default class RoomsListView extends React.Component {
 	static propTypes = {
 		navigation: PropTypes.object.isRequired,
@@ -51,7 +50,7 @@ export default class RoomsListView extends React.Component {
 			searchText: ''
 		};
 		this._keyExtractor = this._keyExtractor.bind(this);
-		this.data = database.objects('subscriptions').sorted('roomUpdatedAt', true);
+		this.data = database.objects('subscriptions').filtered('archived != true').sorted('roomUpdatedAt', true);
 	}
 
 	componentDidMount() {
@@ -67,7 +66,7 @@ export default class RoomsListView extends React.Component {
 	componentWillReceiveProps(props) {
 		if (this.props.server !== props.server) {
 			this.data.removeListener(this.updateState);
-			this.data = database.objects('subscriptions').sorted('roomUpdatedAt', true);
+			this.data = database.objects('subscriptions').filtered('archived != true').sorted('roomUpdatedAt', true);
 			this.data.addListener(this.updateState);
 		} else if (this.props.searchText !== props.searchText) {
 			this.search(props.searchText);
@@ -97,7 +96,7 @@ export default class RoomsListView extends React.Component {
 			});
 		}
 
-		let data = this.data.filtered('name CONTAINS[c] $0', searchText).slice(0, 7);
+		let data = database.objects('subscriptions').filtered('name CONTAINS[c] $0', searchText).slice(0, 7);
 
 		const usernames = data.map(sub => sub.map);
 		try {
diff --git a/app/views/Styles.js b/app/views/Styles.js
index 113dd875c..63c17bac6 100644
--- a/app/views/Styles.js
+++ b/app/views/Styles.js
@@ -1,5 +1,7 @@
 import { StyleSheet, Dimensions, Platform } from 'react-native';
 
+import { COLOR_DANGER } from '../constants/colors';
+
 export default StyleSheet.create({
 	container: {
 		backgroundColor: 'white',
@@ -56,7 +58,7 @@ export default StyleSheet.create({
 		color: '#2f343d'
 	},
 	label_error: {
-		color: 'red',
+		color: COLOR_DANGER,
 		flexGrow: 1,
 		paddingHorizontal: 0,
 		marginBottom: 20
@@ -83,10 +85,14 @@ export default StyleSheet.create({
 		borderColor: 'rgba(0,0,0,.15)',
 		color: 'black'
 	},
+	buttonContainerLastChild: {
+		marginBottom: 40
+	},
 	buttonContainer: {
 		paddingVertical: 15,
 		backgroundColor: '#414852',
-		marginBottom: 20
+		marginBottom: 20,
+		borderRadius: 2
 	},
 	buttonContainer_white: {
 		paddingVertical: 15,
@@ -117,7 +123,7 @@ export default StyleSheet.create({
 	},
 	error: {
 		textAlign: 'center',
-		color: 'red',
+		color: COLOR_DANGER,
 		paddingTop: 5
 	},
 	loading: {
@@ -166,7 +172,7 @@ export default StyleSheet.create({
 		color: 'green'
 	},
 	invalidText: {
-		color: 'red'
+		color: COLOR_DANGER
 	},
 	validatingText: {
 		color: '#aaa'
@@ -177,7 +183,7 @@ export default StyleSheet.create({
 		alignItems: 'center',
 		justifyContent: 'center',
 		margin: 4,
-		borderRadius: 4
+		borderRadius: 2
 	},
 	facebookButton: {
 		backgroundColor: '#3b5998'
@@ -208,5 +214,24 @@ export default StyleSheet.create({
 	},
 	oAuthModal: {
 		margin: 0
+	},
+	status: {
+		position: 'absolute',
+		bottom: -3,
+		right: -3,
+		borderWidth: 3,
+		borderColor: '#fff',
+		borderRadius: 16,
+		width: 16,
+		height: 16
+	},
+	alignItemsFlexEnd: {
+		alignItems: 'flex-end'
+	},
+	textAlignRight: {
+		textAlign: 'right'
+	},
+	opacity5: {
+		opacity: 0.5
 	}
 });
diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj
index 3b7d702e7..8e899c110 100644
--- a/ios/RocketChatRN.xcodeproj/project.pbxproj
+++ b/ios/RocketChatRN.xcodeproj/project.pbxproj
@@ -5,6 +5,7 @@
 	};
 	objectVersion = 46;
 	objects = {
+
 /* Begin PBXBuildFile section */
 		00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
 		00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@@ -49,6 +50,7 @@
 		74815BBCB91147C08C8F7B3D /* libRNAudio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1142E3442BA94B19BCF52814 /* libRNAudio.a */; };
 		77C35F50C01C43668188886C /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A0EEFAF8AB14F5B9E796CDD /* libRNVectorIcons.a */; };
 		7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */; };
+		7AFB806E205AE65700D004E7 /* libRCTToast.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AFB804C205AE63100D004E7 /* libRCTToast.a */; };
 		832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
 		8A159EDB97C44E52AF62D69C /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */; };
 		8ECBD927DDAC4987B98E102E /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */; };
@@ -327,6 +329,13 @@
 			remoteGlobalIDString = 3D7682761D8E76B80014119E;
 			remoteInfo = SplashScreen;
 		};
+		7AFB804B205AE63100D004E7 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = 327633421BFAAD7E004DA88E;
+			remoteInfo = RCTToast;
+		};
 		832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
@@ -485,6 +494,7 @@
 		78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
 		7A30DA4B2D474348824CD05B /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
 		7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCustomInputController.xcodeproj; path = "../node_modules/react-native-keyboard-input/lib/ios/RCTCustomInputController.xcodeproj"; sourceTree = "<group>"; };
+		7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTToast.xcodeproj; path = "../node_modules/@remobile/react-native-toast/ios/RCTToast.xcodeproj"; sourceTree = "<group>"; };
 		832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
 		8A2DD67ADD954AD9873F45FC /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
 		9A1E1766CCB84C91A62BD5A6 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; };
@@ -518,6 +528,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7AFB806E205AE65700D004E7 /* libRCTToast.a in Frameworks */,
 				B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */,
 				7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */,
 				146834051AC3E58100842450 /* libReact.a in Frameworks */,
@@ -756,9 +767,18 @@
 			name = Products;
 			sourceTree = "<group>";
 		};
+		7AFB8036205AE63000D004E7 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				7AFB804C205AE63100D004E7 /* libRCTToast.a */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
 		832341AE1AAA6A7D00B99B32 /* Libraries */ = {
 			isa = PBXGroup;
 			children = (
+				7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */,
 				B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */,
 				7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */,
 				B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */,
@@ -1091,6 +1111,10 @@
 					ProductGroup = 832341B11AAA6A8300B99B32 /* Products */;
 					ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
 				},
+				{
+					ProductGroup = 7AFB8036205AE63000D004E7 /* Products */;
+					ProjectRef = 7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */;
+				},
 				{
 					ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */;
 					ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */;
@@ -1400,6 +1424,13 @@
 			remoteRef = 7ADCFEBF1FEA8A7A00763ED8 /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
+		7AFB804C205AE63100D004E7 /* libRCTToast.a */ = {
+			isa = PBXReferenceProxy;
+			fileType = archive.ar;
+			path = libRCTToast.a;
+			remoteRef = 7AFB804B205AE63100D004E7 /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
 		832341B51AAA6A8300B99B32 /* libRCTText.a */ = {
 			isa = PBXReferenceProxy;
 			fileType = archive.ar;
@@ -1771,6 +1802,7 @@
 					"$(SRCROOT)/../node_modules/react-native-splash-screen/ios",
 					"$(SRCROOT)/../node_modules/react-native-safari-view",
 					"$(SRCROOT)/../node_modules/react-native-audio/ios",
+					"$(SRCROOT)/../../../react-native/React/**",
 				);
 				INFOPLIST_FILE = RocketChatRN/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1811,6 +1843,7 @@
 					"$(SRCROOT)/../node_modules/react-native-splash-screen/ios",
 					"$(SRCROOT)/../node_modules/react-native-safari-view",
 					"$(SRCROOT)/../node_modules/react-native-audio/ios",
+					"$(SRCROOT)/../../../react-native/React/**",
 				);
 				INFOPLIST_FILE = RocketChatRN/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-- 
GitLab