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',
|
'OPEN',
|
||||||
'CLOSE',
|
'CLOSE',
|
||||||
'LEAVE',
|
'LEAVE',
|
||||||
|
'ERASE',
|
||||||
'USER_TYPING',
|
'USER_TYPING',
|
||||||
'MESSAGE_RECEIVED',
|
'MESSAGE_RECEIVED',
|
||||||
'SET_LAST_OPEN',
|
'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 METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']);
|
||||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
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 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 PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']);
|
||||||
export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function requestActiveUser(users) {
|
|
||||||
return {
|
|
||||||
type: types.ACTIVE_USERS.REQUEST,
|
|
||||||
users
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setActiveUser(data) {
|
export function setActiveUser(data) {
|
||||||
return {
|
return {
|
||||||
type: types.ACTIVE_USERS.SET,
|
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) {
|
export function userTyping(status = true) {
|
||||||
return {
|
return {
|
||||||
type: types.ROOM.USER_TYPING,
|
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 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 ESLINT_FIX = null;
|
||||||
|
export const COLOR_DANGER = '#f5455c';
|
||||||
export const STATUS_COLORS = {
|
export const STATUS_COLORS = {
|
||||||
online: '#2de0a5',
|
online: '#2de0a5',
|
||||||
busy: '#f5455c',
|
busy: COLOR_DANGER,
|
||||||
away: '#ffd21f',
|
away: '#ffd21f',
|
||||||
offline: '#cbced1'
|
offline: '#cbced1'
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,8 +17,8 @@ import {
|
||||||
toggleReactionPicker
|
toggleReactionPicker
|
||||||
} from '../actions/messages';
|
} from '../actions/messages';
|
||||||
import { showToast } from '../utils/info';
|
import { showToast } from '../utils/info';
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
const returnAnArray = obj => obj || [];
|
|
||||||
@connect(
|
@connect(
|
||||||
state => ({
|
state => ({
|
||||||
showActions: state.messages.showActions,
|
showActions: state.messages.showActions,
|
||||||
|
@ -79,10 +79,6 @@ export default class MessageActions extends React.Component {
|
||||||
};
|
};
|
||||||
this.handleActionPress = this.handleActionPress.bind(this);
|
this.handleActionPress = this.handleActionPress.bind(this);
|
||||||
this.options = [''];
|
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);
|
this.setPermissions(this.props.permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +123,7 @@ export default class MessageActions extends React.Component {
|
||||||
this.PIN_INDEX = this.options.length - 1;
|
this.PIN_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
// Reaction
|
// Reaction
|
||||||
if (!this.isRoomReadOnly()) {
|
if (!this.isRoomReadOnly() || this.canReactWhenReadOnly()) {
|
||||||
this.options.push('Add Reaction');
|
this.options.push('Add Reaction');
|
||||||
this.REACTION_INDEX = this.options.length - 1;
|
this.REACTION_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
@ -171,19 +167,20 @@ export default class MessageActions extends React.Component {
|
||||||
this.setPermissions(this.props.permissions);
|
this.setPermissions(this.props.permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPermissions(permissions) {
|
setPermissions() {
|
||||||
this.hasEditPermission = returnAnArray(permissions['edit-message'])
|
const permissions = ['edit-message', 'delete-message', 'force-delete-message'];
|
||||||
.some(item => this.mergedRoles.indexOf(item) !== -1);
|
const result = RocketChat.hasPermission(permissions, this.props.room.rid);
|
||||||
this.hasDeletePermission = returnAnArray(permissions['delete-message'])
|
this.hasEditPermission = result[permissions[0]];
|
||||||
.some(item => this.mergedRoles.indexOf(item) !== -1);
|
this.hasDeletePermission = result[permissions[1]];
|
||||||
this.hasForceDeletePermission = returnAnArray(permissions['force-delete-message'])
|
this.hasForceDeletePermission = result[permissions[2]];
|
||||||
.some(item => this.mergedRoles.indexOf(item) !== -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
|
isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
|
||||||
|
|
||||||
isRoomReadOnly = () => this.props.room.ro;
|
isRoomReadOnly = () => this.props.room.ro;
|
||||||
|
|
||||||
|
canReactWhenReadOnly = () => this.props.room.reactWhenReadOnly;
|
||||||
|
|
||||||
allowEdit = (props) => {
|
allowEdit = (props) => {
|
||||||
if (this.isRoomReadOnly()) {
|
if (this.isRoomReadOnly()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -489,6 +489,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
ref={component => this.component = component}
|
ref={component => this.component = component}
|
||||||
style={styles.textBoxInput}
|
style={styles.textBoxInput}
|
||||||
returnKeyType='default'
|
returnKeyType='default'
|
||||||
|
keyboardType='twitter'
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
placeholder='New Message'
|
placeholder='New Message'
|
||||||
onChangeText={text => this.onChangeText(text)}
|
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,
|
onReactionPress: PropTypes.func,
|
||||||
style: ViewPropTypes.style,
|
style: ViewPropTypes.style,
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
_updatedAt: PropTypes.instanceOf(Date)
|
_updatedAt: PropTypes.instanceOf(Date),
|
||||||
|
archived: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLongPress: () => {},
|
onLongPress: () => {},
|
||||||
_updatedAt: new Date()
|
_updatedAt: new Date(),
|
||||||
|
archived: false
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -121,6 +123,14 @@ export default class Message extends React.Component {
|
||||||
message = `${ msg } was set ${ role } by ${ u.username }`;
|
message = `${ msg } was set ${ role } by ${ u.username }`;
|
||||||
} else if (t === 'subscription-role-removed') {
|
} else if (t === 'subscription-role-removed') {
|
||||||
message = `${ msg } is no longer ${ role } by ${ u.username }`;
|
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;
|
return message;
|
||||||
|
@ -130,7 +140,21 @@ export default class Message extends React.Component {
|
||||||
|
|
||||||
isInfoMessage() {
|
isInfoMessage() {
|
||||||
return [
|
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);
|
].includes(this.props.item.t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +260,7 @@ export default class Message extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, message, editing, baseUrl, customEmojis, style
|
item, message, editing, baseUrl, customEmojis, style, archived
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const username = item.alias || item.u.username;
|
const username = item.alias || item.u.username;
|
||||||
const isEditing = message._id === item._id && editing;
|
const isEditing = message._id === item._id && editing;
|
||||||
|
@ -246,7 +270,7 @@ export default class Message extends React.Component {
|
||||||
<TouchableHighlight
|
<TouchableHighlight
|
||||||
onPress={() => this.onPress()}
|
onPress={() => this.onPress()}
|
||||||
onLongPress={() => this.onLongPress()}
|
onLongPress={() => this.onLongPress()}
|
||||||
disabled={this.isDeleted() || this.hasError()}
|
disabled={this.isDeleted() || this.hasError() || archived}
|
||||||
underlayColor='#FFFFFF'
|
underlayColor='#FFFFFF'
|
||||||
activeOpacity={0.3}
|
activeOpacity={0.3}
|
||||||
style={[styles.message, isEditing ? styles.editing : null, style]}
|
style={[styles.message, isEditing ? styles.editing : null, style]}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import MentionedMessagesView from '../../views/MentionedMessagesView';
|
||||||
import SnippetedMessagesView from '../../views/SnippetedMessagesView';
|
import SnippetedMessagesView from '../../views/SnippetedMessagesView';
|
||||||
import RoomFilesView from '../../views/RoomFilesView';
|
import RoomFilesView from '../../views/RoomFilesView';
|
||||||
import RoomMembersView from '../../views/RoomMembersView';
|
import RoomMembersView from '../../views/RoomMembersView';
|
||||||
|
import RoomInfoView from '../../views/RoomInfoView';
|
||||||
|
import RoomInfoEditView from '../../views/RoomInfoEditView';
|
||||||
|
|
||||||
const AuthRoutes = StackNavigator(
|
const AuthRoutes = StackNavigator(
|
||||||
{
|
{
|
||||||
|
@ -89,6 +91,20 @@ const AuthRoutes = StackNavigator(
|
||||||
title: 'Room Members',
|
title: 'Room Members',
|
||||||
headerTintColor: '#292E35'
|
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) {
|
shouldComponentUpdate(nextProps) {
|
||||||
const userId = this.props.id;
|
const userId = this.props.id;
|
||||||
return this.status !== nextProps.activeUsers[userId];
|
return (nextProps.activeUsers[userId] && nextProps.activeUsers[userId].status) !== this.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
const userId = this.props.id;
|
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() {
|
render() {
|
||||||
|
|
|
@ -80,8 +80,6 @@ const subscriptionSchema = {
|
||||||
roles: { type: 'list', objectType: 'subscriptionRolesSchema' },
|
roles: { type: 'list', objectType: 'subscriptionRolesSchema' },
|
||||||
unread: { type: 'int', optional: true },
|
unread: { type: 'int', optional: true },
|
||||||
userMentions: { type: 'int', optional: true },
|
userMentions: { type: 'int', optional: true },
|
||||||
// userMentions: 0,
|
|
||||||
// groupMentions: 0,
|
|
||||||
roomUpdatedAt: { type: 'date', optional: true },
|
roomUpdatedAt: { type: 'date', optional: true },
|
||||||
ro: { type: 'bool', optional: true },
|
ro: { type: 'bool', optional: true },
|
||||||
lastOpen: { type: 'date', optional: true },
|
lastOpen: { type: 'date', optional: true },
|
||||||
|
@ -89,7 +87,10 @@ const subscriptionSchema = {
|
||||||
description: { type: 'string', optional: true },
|
description: { type: 'string', optional: true },
|
||||||
announcement: { type: 'string', optional: true },
|
announcement: { type: 'string', optional: true },
|
||||||
topic: { 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 = [
|
const schema = [
|
||||||
settingsSchema,
|
settingsSchema,
|
||||||
subscriptionSchema,
|
subscriptionSchema,
|
||||||
|
@ -254,7 +264,8 @@ const schema = [
|
||||||
customEmojiAliasesSchema,
|
customEmojiAliasesSchema,
|
||||||
customEmojisSchema,
|
customEmojisSchema,
|
||||||
messagesReactionsSchema,
|
messagesReactionsSchema,
|
||||||
messagesReactionsUsernamesSchema
|
messagesReactionsUsernamesSchema,
|
||||||
|
rolesSchema
|
||||||
];
|
];
|
||||||
class DB {
|
class DB {
|
||||||
databases = {
|
databases = {
|
||||||
|
|
|
@ -12,12 +12,13 @@ import * as actions from '../actions';
|
||||||
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
||||||
import { setUser, setLoginServices, removeLoginServices } from '../actions/login';
|
import { setUser, setLoginServices, removeLoginServices } from '../actions/login';
|
||||||
import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect';
|
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 { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
||||||
import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
|
import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
|
||||||
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
|
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
|
||||||
import { snippetedMessagesReceived } from '../actions/snippetedMessages';
|
import { snippetedMessagesReceived } from '../actions/snippetedMessages';
|
||||||
import { roomFilesReceived } from '../actions/roomFiles';
|
import { roomFilesReceived } from '../actions/roomFiles';
|
||||||
|
import { setRoles } from '../actions/roles';
|
||||||
import Ddp from './ddp';
|
import Ddp from './ddp';
|
||||||
|
|
||||||
export { Accounts } from 'react-native-meteor';
|
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 TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||||
const SERVER_TIMEOUT = 30000;
|
const SERVER_TIMEOUT = 30000;
|
||||||
|
|
||||||
|
const returnAnArray = obj => obj || [];
|
||||||
|
|
||||||
const normalizeMessage = (lastMessage) => {
|
const normalizeMessage = (lastMessage) => {
|
||||||
if (lastMessage) {
|
if (lastMessage) {
|
||||||
|
@ -91,13 +93,12 @@ const RocketChat = {
|
||||||
this._setUserTimer = null;
|
this._setUserTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this._setUserTimer = setTimeout(() => {
|
this._setUserTimer = setTimeout(() => {
|
||||||
reduxStore.dispatch(requestActiveUser(this.activeUsers));
|
reduxStore.dispatch(setActiveUser(this.activeUsers));
|
||||||
this._setUserTimer = null;
|
this._setUserTimer = null;
|
||||||
return this.activeUsers = {};
|
return this.activeUsers = {};
|
||||||
}, 5000);
|
}, 3000);
|
||||||
this.activeUsers[ddpMessage.id] = status;
|
this.activeUsers[ddpMessage.id] = ddpMessage.fields;
|
||||||
},
|
},
|
||||||
reconnect() {
|
reconnect() {
|
||||||
if (this.ddp) {
|
if (this.ddp) {
|
||||||
|
@ -124,6 +125,8 @@ const RocketChat = {
|
||||||
RocketChat.getSettings();
|
RocketChat.getSettings();
|
||||||
RocketChat.getPermissions();
|
RocketChat.getPermissions();
|
||||||
RocketChat.getCustomEmoji();
|
RocketChat.getCustomEmoji();
|
||||||
|
this.ddp.subscribe('activeUsers');
|
||||||
|
this.ddp.subscribe('roles');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ddp.on('error', (err) => {
|
this.ddp.on('error', (err) => {
|
||||||
|
@ -131,8 +134,6 @@ const RocketChat = {
|
||||||
reduxStore.dispatch(connectFailure());
|
reduxStore.dispatch(connectFailure());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ddp.on('connected', () => this.ddp.subscribe('activeUsers', null, false));
|
|
||||||
|
|
||||||
this.ddp.on('users', ddpMessage => RocketChat._setUser(ddpMessage));
|
this.ddp.on('users', ddpMessage => RocketChat._setUser(ddpMessage));
|
||||||
|
|
||||||
this.ddp.on('stream-room-messages', (ddpMessage) => {
|
this.ddp.on('stream-room-messages', (ddpMessage) => {
|
||||||
|
@ -171,6 +172,12 @@ const RocketChat = {
|
||||||
sub.roomUpdatedAt = data._updatedAt;
|
sub.roomUpdatedAt = data._updatedAt;
|
||||||
sub.lastMessage = normalizeMessage(data.lastMessage);
|
sub.lastMessage = normalizeMessage(data.lastMessage);
|
||||||
sub.ro = data.ro;
|
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.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);
|
}).catch(console.log);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -649,6 +678,9 @@ const RocketChat = {
|
||||||
subscription.description = room.description;
|
subscription.description = room.description;
|
||||||
subscription.topic = room.topic;
|
subscription.topic = room.topic;
|
||||||
subscription.announcement = room.announcement;
|
subscription.announcement = room.announcement;
|
||||||
|
subscription.reactWhenReadOnly = room.reactWhenReadOnly;
|
||||||
|
subscription.archived = room.archived;
|
||||||
|
subscription.joinCodeRequired = room.joinCodeRequired;
|
||||||
}
|
}
|
||||||
if (subscription.roles) {
|
if (subscription.roles) {
|
||||||
subscription.roles = subscription.roles.map(role => ({ value: role }));
|
subscription.roles = subscription.roles.map(role => ({ value: role }));
|
||||||
|
@ -823,6 +855,17 @@ const RocketChat = {
|
||||||
getRoomMembers(rid, allUsers) {
|
getRoomMembers(rid, allUsers) {
|
||||||
return call('getUsersOfRoom', 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) {
|
toggleBlockUser(rid, blocked, block) {
|
||||||
if (block) {
|
if (block) {
|
||||||
return call('blockUser', { rid, blocked });
|
return call('blockUser', { rid, blocked });
|
||||||
|
@ -831,6 +874,40 @@ const RocketChat = {
|
||||||
},
|
},
|
||||||
leaveRoom(rid) {
|
leaveRoom(rid) {
|
||||||
return call('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 permissions from './permissions';
|
||||||
import customEmojis from './customEmojis';
|
import customEmojis from './customEmojis';
|
||||||
import activeUsers from './activeUsers';
|
import activeUsers from './activeUsers';
|
||||||
|
import roles from './roles';
|
||||||
import starredMessages from './starredMessages';
|
import starredMessages from './starredMessages';
|
||||||
import pinnedMessages from './pinnedMessages';
|
import pinnedMessages from './pinnedMessages';
|
||||||
import mentionedMessages from './mentionedMessages';
|
import mentionedMessages from './mentionedMessages';
|
||||||
|
@ -32,6 +33,7 @@ export default combineReducers({
|
||||||
permissions,
|
permissions,
|
||||||
customEmojis,
|
customEmojis,
|
||||||
activeUsers,
|
activeUsers,
|
||||||
|
roles,
|
||||||
starredMessages,
|
starredMessages,
|
||||||
pinnedMessages,
|
pinnedMessages,
|
||||||
mentionedMessages,
|
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 createChannel from './createChannel';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import state from './state';
|
import state from './state';
|
||||||
import activeUsers from './activeUsers';
|
|
||||||
import starredMessages from './starredMessages';
|
import starredMessages from './starredMessages';
|
||||||
import pinnedMessages from './pinnedMessages';
|
import pinnedMessages from './pinnedMessages';
|
||||||
import mentionedMessages from './mentionedMessages';
|
import mentionedMessages from './mentionedMessages';
|
||||||
|
@ -26,7 +25,6 @@ const root = function* root() {
|
||||||
messages(),
|
messages(),
|
||||||
selectServer(),
|
selectServer(),
|
||||||
state(),
|
state(),
|
||||||
activeUsers(),
|
|
||||||
starredMessages(),
|
starredMessages(),
|
||||||
pinnedMessages(),
|
pinnedMessages(),
|
||||||
mentionedMessages(),
|
mentionedMessages(),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as actions from '../actions';
|
||||||
import { setServer } from '../actions/server';
|
import { setServer } from '../actions/server';
|
||||||
import { restoreToken } from '../actions/login';
|
import { restoreToken } from '../actions/login';
|
||||||
import { APP } from '../actions/actionsTypes';
|
import { APP } from '../actions/actionsTypes';
|
||||||
|
import { setRoles } from '../actions/roles';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
|
@ -23,6 +24,11 @@ const restore = function* restore() {
|
||||||
yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
|
yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
|
||||||
const emojis = database.objects('customEmojis');
|
const emojis = database.objects('customEmojis');
|
||||||
yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length))));
|
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({}));
|
yield put(actions.appReady({}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import database from '../lib/realm';
|
||||||
import * as NavigationService from '../containers/routes/NavigationService';
|
import * as NavigationService from '../containers/routes/NavigationService';
|
||||||
|
|
||||||
const leaveRoom = rid => RocketChat.leaveRoom(rid);
|
const leaveRoom = rid => RocketChat.leaveRoom(rid);
|
||||||
|
const eraseRoom = rid => RocketChat.eraseRoom(rid);
|
||||||
|
|
||||||
const getRooms = function* getRooms() {
|
const getRooms = function* getRooms() {
|
||||||
return yield RocketChat.getRooms();
|
return yield RocketChat.getRooms();
|
||||||
|
@ -121,9 +122,7 @@ const updateLastOpen = function* updateLastOpen() {
|
||||||
yield put(setLastOpen());
|
yield put(setLastOpen());
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) {
|
||||||
try {
|
|
||||||
yield call(leaveRoom, rid);
|
|
||||||
NavigationService.goRoomsList();
|
NavigationService.goRoomsList();
|
||||||
yield delay(1000);
|
yield delay(1000);
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
|
@ -132,15 +131,30 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
||||||
const subscription = database.objects('subscriptions').filtered('rid = $0', rid);
|
const subscription = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
database.delete(subscription);
|
database.delete(subscription);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
||||||
|
try {
|
||||||
|
yield call(leaveRoom, rid);
|
||||||
|
yield goRoomsListAndDelete(rid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.error === 'error-you-are-last-owner') {
|
if (e.error === 'error-you-are-last-owner') {
|
||||||
Alert.alert('You are the last owner. Please set new owner before leaving the room.');
|
Alert.alert('You are the last owner. Please set new owner before leaving the room.');
|
||||||
} else {
|
} 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() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
|
yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
|
||||||
yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest);
|
yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest);
|
||||||
|
@ -150,5 +164,6 @@ const root = function* root() {
|
||||||
yield takeLatest(FOREGROUND, watchRoomsRequest);
|
yield takeLatest(FOREGROUND, watchRoomsRequest);
|
||||||
yield takeLatest(BACKGROUND, updateLastOpen);
|
yield takeLatest(BACKGROUND, updateLastOpen);
|
||||||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||||
|
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -6,7 +6,9 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
|
import Status from '../../containers/status';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
@ -33,13 +35,15 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
this.state = {
|
this.state = {
|
||||||
sections: [],
|
sections: [],
|
||||||
room: {},
|
room: {},
|
||||||
members: []
|
members: [],
|
||||||
|
member: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await this.updateRoom();
|
await this.updateRoom();
|
||||||
this.updateRoomMembers();
|
this.updateRoomMembers();
|
||||||
|
this.updateRoomMember();
|
||||||
this.rooms.addListener(this.updateRoom);
|
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);
|
getRoomTitle = room => (room.t === 'd' ? room.fname : room.name);
|
||||||
|
|
||||||
updateRoomMembers = async() => {
|
updateRoomMembers = async() => {
|
||||||
let members;
|
let members = [];
|
||||||
try {
|
try {
|
||||||
const membersResult = await RocketChat.getRoomMembers(this.state.room.rid, false);
|
const membersResult = await RocketChat.getRoomMembers(this.state.room.rid, false);
|
||||||
members = membersResult.records;
|
members = membersResult.records;
|
||||||
|
@ -70,6 +74,17 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
this.updateSections();
|
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() => {
|
updateRoom = async() => {
|
||||||
const [room] = this.rooms;
|
const [room] = this.rooms;
|
||||||
await this.setState({ room });
|
await this.setState({ room });
|
||||||
|
@ -80,12 +95,17 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
const { rid, t, blocked } = this.state.room;
|
const { rid, t, blocked } = this.state.room;
|
||||||
const { members } = this.state;
|
const { members } = this.state;
|
||||||
const sections = [{
|
const sections = [{
|
||||||
data: [{ icon: 'ios-star', name: 'USER' }],
|
data: [{
|
||||||
|
icon: 'ios-star',
|
||||||
|
name: 'USER',
|
||||||
|
route: 'RoomInfo',
|
||||||
|
params: { rid }
|
||||||
|
}],
|
||||||
renderItem: this.renderRoomInfo
|
renderItem: this.renderRoomInfo
|
||||||
}, {
|
}, {
|
||||||
data: [
|
data: [
|
||||||
{ icon: 'ios-call-outline', name: 'Voice call' },
|
{ icon: 'ios-call-outline', name: 'Voice call', disabled: true },
|
||||||
{ icon: 'ios-videocam-outline', name: 'Video call' }
|
{ icon: 'ios-videocam-outline', name: 'Video call', disabled: true }
|
||||||
],
|
],
|
||||||
renderItem: this.renderItem
|
renderItem: this.renderItem
|
||||||
}, {
|
}, {
|
||||||
|
@ -108,8 +128,8 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
route: 'StarredMessages',
|
route: 'StarredMessages',
|
||||||
params: { rid }
|
params: { rid }
|
||||||
},
|
},
|
||||||
{ icon: 'ios-search', name: 'Search' },
|
{ icon: 'ios-search', name: 'Search', disabled: true },
|
||||||
{ icon: 'ios-share-outline', name: 'Share' },
|
{ icon: 'ios-share-outline', name: 'Share', disabled: true },
|
||||||
{
|
{
|
||||||
icon: 'ios-pin',
|
icon: 'ios-pin',
|
||||||
name: 'Pinned',
|
name: 'Pinned',
|
||||||
|
@ -122,14 +142,14 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
route: 'SnippetedMessages',
|
route: 'SnippetedMessages',
|
||||||
params: { rid }
|
params: { rid }
|
||||||
},
|
},
|
||||||
{ icon: 'ios-notifications-outline', name: 'Notifications preferences' }
|
{ icon: 'ios-notifications-outline', name: 'Notifications preferences', disabled: true }
|
||||||
],
|
],
|
||||||
renderItem: this.renderItem
|
renderItem: this.renderItem
|
||||||
}];
|
}];
|
||||||
if (t === 'd') {
|
if (t === 'd') {
|
||||||
sections.push({
|
sections.push({
|
||||||
data: [
|
data: [
|
||||||
{ icon: 'ios-volume-off', name: 'Mute user' },
|
{ icon: 'ios-volume-off', name: 'Mute user', disabled: true },
|
||||||
{
|
{
|
||||||
icon: 'block',
|
icon: 'block',
|
||||||
name: `${ blocked ? 'Unblock' : 'Block' } user`,
|
name: `${ blocked ? 'Unblock' : 'Block' } user`,
|
||||||
|
@ -151,7 +171,7 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
}
|
}
|
||||||
sections.push({
|
sections.push({
|
||||||
data: [
|
data: [
|
||||||
{ icon: 'ios-volume-off', name: 'Mute channel' },
|
{ icon: 'ios-volume-off', name: 'Mute channel', disabled: true },
|
||||||
{
|
{
|
||||||
icon: 'block',
|
icon: 'block',
|
||||||
name: 'Leave channel',
|
name: 'Leave channel',
|
||||||
|
@ -167,8 +187,7 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
|
|
||||||
toggleBlockUser = () => {
|
toggleBlockUser = () => {
|
||||||
const { rid, blocked } = this.state.room;
|
const { rid, blocked } = this.state.room;
|
||||||
const { members } = this.state;
|
const { member } = this.state;
|
||||||
const member = members.find(m => m.id !== this.props.user.id);
|
|
||||||
RocketChat.toggleBlockUser(rid, member._id, !blocked);
|
RocketChat.toggleBlockUser(rid, member._id, !blocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,16 +204,14 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
{
|
{
|
||||||
text: 'Yes, leave it!',
|
text: 'Yes, leave it!',
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: async() => {
|
onPress: () => this.props.leaveRoom(room.rid)
|
||||||
this.props.leaveRoom(room.rid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderRoomInfo = ({ item }) => {
|
renderRoomInfo = ({ item }) => {
|
||||||
const { room } = this.state;
|
const { room, member } = this.state;
|
||||||
const { name, t, topic } = room;
|
const { name, t, topic } = room;
|
||||||
return (
|
return (
|
||||||
this.renderTouchableItem([
|
this.renderTouchableItem([
|
||||||
|
@ -205,12 +222,14 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={this.props.baseUrl}
|
||||||
type={t}
|
type={t}
|
||||||
/>,
|
>
|
||||||
|
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||||
|
</Avatar>,
|
||||||
<View key='name' style={styles.roomTitleContainer}>
|
<View key='name' style={styles.roomTitleContainer}>
|
||||||
<Text style={styles.roomTitle}>{ this.getRoomTitle(room) }</Text>
|
<Text style={styles.roomTitle}>{ this.getRoomTitle(room) }</Text>
|
||||||
<Text style={styles.roomDescription} ellipsizeMode='tail' numberOfLines={1}>{t === 'd' ? `@${ name }` : topic}</Text>
|
<Text style={styles.roomDescription} ellipsizeMode='tail' numberOfLines={1}>{t === 'd' ? `@${ name }` : topic}</Text>
|
||||||
</View>,
|
</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)
|
], item)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -223,7 +242,7 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
accessibilityLabel={item.name}
|
accessibilityLabel={item.name}
|
||||||
accessibilityTraits='button'
|
accessibilityTraits='button'
|
||||||
>
|
>
|
||||||
<View style={styles.sectionItem}>
|
<View style={[styles.sectionItem, item.disabled && styles.sectionItemDisabled]}>
|
||||||
{subview}
|
{subview}
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</Touch>
|
||||||
|
@ -241,11 +260,13 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
<Icon key='left-icon' name={item.icon} size={24} style={styles.sectionItemIcon} />,
|
<Icon key='left-icon' name={item.icon} size={24} style={styles.sectionItemIcon} />,
|
||||||
<Text key='name' style={styles.sectionItemName}>{ item.name }</Text>,
|
<Text key='name' style={styles.sectionItemName}>{ item.name }</Text>,
|
||||||
item.description && <Text key='description' style={styles.sectionItemDescription}>{ item.description }</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);
|
return this.renderTouchableItem(subview, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderSeparator = () => <View style={styles.separator} />;
|
||||||
|
|
||||||
renderSectionSeparator = (data) => {
|
renderSectionSeparator = (data) => {
|
||||||
if (!data.trailingItem) {
|
if (!data.trailingItem) {
|
||||||
if (!data.trailingSection) {
|
if (!data.trailingSection) {
|
||||||
|
@ -265,6 +286,7 @@ export default class RoomActionsView extends React.PureComponent {
|
||||||
stickySectionHeadersEnabled={false}
|
stickySectionHeadersEnabled={false}
|
||||||
sections={this.state.sections}
|
sections={this.state.sections}
|
||||||
SectionSeparatorComponent={this.renderSectionSeparator}
|
SectionSeparatorComponent={this.renderSectionSeparator}
|
||||||
|
ItemSeparatorComponent={this.renderSeparator}
|
||||||
keyExtractor={(item, index) => index}
|
keyExtractor={(item, index) => index}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,10 +13,13 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
sectionItem: {
|
sectionItem: {
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
paddingVertical: 10,
|
paddingVertical: 16,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
|
sectionItemDisabled: {
|
||||||
|
opacity: 0.3
|
||||||
|
},
|
||||||
sectionItemIcon: {
|
sectionItemIcon: {
|
||||||
width: 45,
|
width: 45,
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
|
@ -25,7 +28,11 @@ export default StyleSheet.create({
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
sectionItemDescription: {
|
sectionItemDescription: {
|
||||||
color: '#cbced1'
|
color: '#ccc'
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: StyleSheet.hairlineWidth,
|
||||||
|
backgroundColor: '#ddd'
|
||||||
},
|
},
|
||||||
sectionSeparator: {
|
sectionSeparator: {
|
||||||
height: 10,
|
height: 10,
|
||||||
|
@ -49,6 +56,6 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
roomDescription: {
|
roomDescription: {
|
||||||
fontSize: 12,
|
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 { connect } from 'react-redux';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import Status from '../../containers/status';
|
import Status from '../../containers/status';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
|
@ -115,7 +116,7 @@ export default class MentionedMessagesView extends React.PureComponent {
|
||||||
accessibilityTraits='button'
|
accessibilityTraits='button'
|
||||||
>
|
>
|
||||||
<View style={styles.item}>
|
<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>
|
<Text style={styles.username}>{item.username}</Text>
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</Touch>
|
||||||
|
|
|
@ -7,7 +7,7 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
paddingVertical: 8,
|
paddingVertical: 10,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
|
@ -15,14 +15,12 @@ export default StyleSheet.create({
|
||||||
marginRight: 16
|
marginRight: 16
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
position: 'absolute',
|
bottom: -2,
|
||||||
bottom: -3,
|
right: -2,
|
||||||
right: -3,
|
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderColor: '#fff',
|
borderRadius: 10,
|
||||||
borderRadius: 12,
|
width: 10,
|
||||||
width: 12,
|
height: 10
|
||||||
height: 12
|
|
||||||
},
|
},
|
||||||
separator: {
|
separator: {
|
||||||
height: StyleSheet.hairlineWidth,
|
height: StyleSheet.hairlineWidth,
|
||||||
|
|
|
@ -50,7 +50,8 @@ export default class RoomHeaderView extends React.PureComponent {
|
||||||
|
|
||||||
getUserStatus() {
|
getUserStatus() {
|
||||||
const userId = this.rid.replace(this.props.user.id, '').trim();
|
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() {
|
getUserStatusLabel() {
|
||||||
|
@ -86,7 +87,12 @@ export default class RoomHeaderView extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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() ?
|
{this.isDirect() ?
|
||||||
<View style={[styles.status, { backgroundColor: STATUS_COLORS[this.getUserStatus()] }]} />
|
<View style={[styles.status, { backgroundColor: STATUS_COLORS[this.getUserStatus()] }]} />
|
||||||
: null
|
: null
|
||||||
|
|
|
@ -157,6 +157,7 @@ export default class RoomView extends React.Component {
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
onReactionPress={this.onReactionPress}
|
onReactionPress={this.onReactionPress}
|
||||||
onLongPress={this.onMessageLongPress}
|
onLongPress={this.onMessageLongPress}
|
||||||
|
archived={this.state.room.archived}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -171,7 +172,7 @@ export default class RoomView extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.state.room.ro) {
|
if (this.state.room.ro || this.state.room.archived) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.readOnly}>
|
<View style={styles.readOnly}>
|
||||||
<Text>This room is read only</Text>
|
<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()),
|
login: () => dispatch(actions.login()),
|
||||||
connect: () => dispatch(server.connectRequest())
|
connect: () => dispatch(server.connectRequest())
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export default class RoomsListView extends React.Component {
|
export default class RoomsListView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object.isRequired,
|
navigation: PropTypes.object.isRequired,
|
||||||
|
@ -51,7 +50,7 @@ export default class RoomsListView extends React.Component {
|
||||||
searchText: ''
|
searchText: ''
|
||||||
};
|
};
|
||||||
this._keyExtractor = this._keyExtractor.bind(this);
|
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() {
|
componentDidMount() {
|
||||||
|
@ -67,7 +66,7 @@ export default class RoomsListView extends React.Component {
|
||||||
componentWillReceiveProps(props) {
|
componentWillReceiveProps(props) {
|
||||||
if (this.props.server !== props.server) {
|
if (this.props.server !== props.server) {
|
||||||
this.data.removeListener(this.updateState);
|
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);
|
this.data.addListener(this.updateState);
|
||||||
} else if (this.props.searchText !== props.searchText) {
|
} else if (this.props.searchText !== props.searchText) {
|
||||||
this.search(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);
|
const usernames = data.map(sub => sub.map);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { StyleSheet, Dimensions, Platform } from 'react-native';
|
import { StyleSheet, Dimensions, Platform } from 'react-native';
|
||||||
|
|
||||||
|
import { COLOR_DANGER } from '../constants/colors';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
|
@ -56,7 +58,7 @@ export default StyleSheet.create({
|
||||||
color: '#2f343d'
|
color: '#2f343d'
|
||||||
},
|
},
|
||||||
label_error: {
|
label_error: {
|
||||||
color: 'red',
|
color: COLOR_DANGER,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
marginBottom: 20
|
marginBottom: 20
|
||||||
|
@ -83,10 +85,14 @@ export default StyleSheet.create({
|
||||||
borderColor: 'rgba(0,0,0,.15)',
|
borderColor: 'rgba(0,0,0,.15)',
|
||||||
color: 'black'
|
color: 'black'
|
||||||
},
|
},
|
||||||
|
buttonContainerLastChild: {
|
||||||
|
marginBottom: 40
|
||||||
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
paddingVertical: 15,
|
paddingVertical: 15,
|
||||||
backgroundColor: '#414852',
|
backgroundColor: '#414852',
|
||||||
marginBottom: 20
|
marginBottom: 20,
|
||||||
|
borderRadius: 2
|
||||||
},
|
},
|
||||||
buttonContainer_white: {
|
buttonContainer_white: {
|
||||||
paddingVertical: 15,
|
paddingVertical: 15,
|
||||||
|
@ -117,7 +123,7 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: 'red',
|
color: COLOR_DANGER,
|
||||||
paddingTop: 5
|
paddingTop: 5
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
|
@ -166,7 +172,7 @@ export default StyleSheet.create({
|
||||||
color: 'green'
|
color: 'green'
|
||||||
},
|
},
|
||||||
invalidText: {
|
invalidText: {
|
||||||
color: 'red'
|
color: COLOR_DANGER
|
||||||
},
|
},
|
||||||
validatingText: {
|
validatingText: {
|
||||||
color: '#aaa'
|
color: '#aaa'
|
||||||
|
@ -177,7 +183,7 @@ export default StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
margin: 4,
|
margin: 4,
|
||||||
borderRadius: 4
|
borderRadius: 2
|
||||||
},
|
},
|
||||||
facebookButton: {
|
facebookButton: {
|
||||||
backgroundColor: '#3b5998'
|
backgroundColor: '#3b5998'
|
||||||
|
@ -208,5 +214,24 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
oAuthModal: {
|
oAuthModal: {
|
||||||
margin: 0
|
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;
|
objectVersion = 46;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
||||||
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.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 */; };
|
74815BBCB91147C08C8F7B3D /* libRNAudio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1142E3442BA94B19BCF52814 /* libRNAudio.a */; };
|
||||||
77C35F50C01C43668188886C /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A0EEFAF8AB14F5B9E796CDD /* libRNVectorIcons.a */; };
|
77C35F50C01C43668188886C /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A0EEFAF8AB14F5B9E796CDD /* libRNVectorIcons.a */; };
|
||||||
7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A430E1E20238C02008F55BC /* libRCTCustomInputController.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 */; };
|
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
|
||||||
8A159EDB97C44E52AF62D69C /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */; };
|
8A159EDB97C44E52AF62D69C /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */; };
|
||||||
8ECBD927DDAC4987B98E102E /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */; };
|
8ECBD927DDAC4987B98E102E /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */; };
|
||||||
|
@ -327,6 +329,13 @@
|
||||||
remoteGlobalIDString = 3D7682761D8E76B80014119E;
|
remoteGlobalIDString = 3D7682761D8E76B80014119E;
|
||||||
remoteInfo = SplashScreen;
|
remoteInfo = SplashScreen;
|
||||||
};
|
};
|
||||||
|
7AFB804B205AE63100D004E7 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = 327633421BFAAD7E004DA88E;
|
||||||
|
remoteInfo = RCTToast;
|
||||||
|
};
|
||||||
832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = {
|
832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
7AFB806E205AE65700D004E7 /* libRCTToast.a in Frameworks */,
|
||||||
B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */,
|
B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */,
|
||||||
7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */,
|
7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */,
|
||||||
146834051AC3E58100842450 /* libReact.a in Frameworks */,
|
146834051AC3E58100842450 /* libReact.a in Frameworks */,
|
||||||
|
@ -756,9 +767,18 @@
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
7AFB8036205AE63000D004E7 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7AFB804C205AE63100D004E7 /* libRCTToast.a */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */,
|
||||||
B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */,
|
B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */,
|
||||||
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */,
|
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */,
|
||||||
B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */,
|
B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */,
|
||||||
|
@ -1091,6 +1111,10 @@
|
||||||
ProductGroup = 832341B11AAA6A8300B99B32 /* Products */;
|
ProductGroup = 832341B11AAA6A8300B99B32 /* Products */;
|
||||||
ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
|
ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ProductGroup = 7AFB8036205AE63000D004E7 /* Products */;
|
||||||
|
ProjectRef = 7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */;
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */;
|
ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */;
|
||||||
ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */;
|
ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */;
|
||||||
|
@ -1400,6 +1424,13 @@
|
||||||
remoteRef = 7ADCFEBF1FEA8A7A00763ED8 /* PBXContainerItemProxy */;
|
remoteRef = 7ADCFEBF1FEA8A7A00763ED8 /* PBXContainerItemProxy */;
|
||||||
sourceTree = BUILT_PRODUCTS_DIR;
|
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 */ = {
|
832341B51AAA6A8300B99B32 /* libRCTText.a */ = {
|
||||||
isa = PBXReferenceProxy;
|
isa = PBXReferenceProxy;
|
||||||
fileType = archive.ar;
|
fileType = archive.ar;
|
||||||
|
@ -1771,6 +1802,7 @@
|
||||||
"$(SRCROOT)/../node_modules/react-native-splash-screen/ios",
|
"$(SRCROOT)/../node_modules/react-native-splash-screen/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-safari-view",
|
"$(SRCROOT)/../node_modules/react-native-safari-view",
|
||||||
"$(SRCROOT)/../node_modules/react-native-audio/ios",
|
"$(SRCROOT)/../node_modules/react-native-audio/ios",
|
||||||
|
"$(SRCROOT)/../../../react-native/React/**",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = RocketChatRN/Info.plist;
|
INFOPLIST_FILE = RocketChatRN/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
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-splash-screen/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-safari-view",
|
"$(SRCROOT)/../node_modules/react-native-safari-view",
|
||||||
"$(SRCROOT)/../node_modules/react-native-audio/ios",
|
"$(SRCROOT)/../node_modules/react-native-audio/ios",
|
||||||
|
"$(SRCROOT)/../../../react-native/React/**",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = RocketChatRN/Info.plist;
|
INFOPLIST_FILE = RocketChatRN/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
|
Loading…
Reference in New Issue