Merge branch 'develop' of github.com:RocketChat/Rocket.Chat.ReactNative into develop
This commit is contained in:
commit
7ab759aa27
|
@ -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']);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function setRoles(data) {
|
||||
return {
|
||||
type: types.ROLES.SET,
|
||||
data
|
||||
};
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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]}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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;
|
|
@ -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(),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,9 +122,7 @@ const updateLastOpen = function* updateLastOpen() {
|
|||
yield put(setLastOpen());
|
||||
};
|
||||
|
||||
const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
||||
try {
|
||||
yield call(leaveRoom, rid);
|
||||
const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) {
|
||||
NavigationService.goRoomsList();
|
||||
yield delay(1000);
|
||||
database.write(() => {
|
||||
|
@ -132,15 +131,30 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
|||
const subscription = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
database.delete(subscription);
|
||||
});
|
||||
};
|
||||
|
||||
const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
||||
try {
|
||||
yield call(leaveRoom, rid);
|
||||
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;
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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} />
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue