[NEW] Room actions: Mentioned messages and Room Members (#242)

* Mentioned messages

* Starred and pinned actions debounce

* Room members

* Open room on member touch
This commit is contained in:
Diego Mello 2018-03-02 12:11:34 -03:00 committed by Guilherme Gazzo
parent 477609375c
commit 4823e3a2e4
28 changed files with 691 additions and 186 deletions

View File

@ -37,6 +37,7 @@ module.exports = {
"react/no-unused-prop-types": [2, { "react/no-unused-prop-types": [2, {
"skipShapeProps": true "skipShapeProps": true
}], }],
"react/no-did-mount-set-state": 0,
"react/no-multi-comp": [0], "react/no-multi-comp": [0],
"react/jsx-indent": [2, "tab"], "react/jsx-indent": [2, "tab"],
"react/jsx-indent-props": [2, "tab"], "react/jsx-indent-props": [2, "tab"],

View File

@ -136,12 +136,15 @@ exports[`render channel 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -300,12 +303,15 @@ exports[`render no icon 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -464,12 +470,15 @@ exports[`render private group 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -640,12 +649,15 @@ exports[`render unread +999 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -838,12 +850,15 @@ exports[`render unread 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -1036,12 +1051,15 @@ exports[`renders correctly 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10

View File

@ -299,12 +299,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -471,12 +474,17 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
Object {
"color": "#1d74f5",
},
]
} }
> >
Nov 10 Nov 10
@ -641,12 +649,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -836,12 +847,17 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
Object {
"color": "#1d74f5",
},
]
} }
> >
Nov 10 Nov 10
@ -1029,12 +1045,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -1222,12 +1241,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -1415,12 +1437,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -1608,12 +1633,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -1801,12 +1829,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -1971,12 +2002,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10
@ -2141,12 +2175,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
numberOfLines={1} numberOfLines={1}
style={ style={
Object { Array [
"alignItems": "center", Object {
"color": "#888", "alignItems": "center",
"fontSize": 10, "color": "#888",
"justifyContent": "center", "fontSize": 10,
} "justifyContent": "center",
},
undefined,
]
} }
> >
Nov 10 Nov 10

View File

@ -93,8 +93,9 @@ 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', 'REQUEST']);
export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGE_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', 'MESSAGE_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 INCREMENT = 'INCREMENT'; export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT'; export const DECREMENT = 'DECREMENT';

View File

@ -0,0 +1,21 @@
import * as types from './actionsTypes';
export function openMentionedMessages(rid) {
return {
type: types.MENTIONED_MESSAGES.OPEN,
rid
};
}
export function closeMentionedMessages() {
return {
type: types.MENTIONED_MESSAGES.CLOSE
};
}
export function mentionedMessagesReceived(messages) {
return {
type: types.MENTIONED_MESSAGES.MESSAGES_RECEIVED,
messages
};
}

View File

@ -13,10 +13,10 @@ export function closePinnedMessages() {
}; };
} }
export function pinnedMessageReceived(message) { export function pinnedMessagesReceived(messages) {
return { return {
type: types.PINNED_MESSAGES.MESSAGE_RECEIVED, type: types.PINNED_MESSAGES.MESSAGES_RECEIVED,
message messages
}; };
} }

View File

@ -13,10 +13,10 @@ export function closeStarredMessages() {
}; };
} }
export function starredMessageReceived(message) { export function starredMessagesReceived(messages) {
return { return {
type: types.STARRED_MESSAGES.MESSAGE_RECEIVED, type: types.STARRED_MESSAGES.MESSAGES_RECEIVED,
message messages
}; };
} }

View File

@ -143,7 +143,7 @@ const Reply = ({ attachment, timeFormat }) => {
{renderTitle()} {renderTitle()}
{renderText()} {renderText()}
{renderFields()} {renderFields()}
{attachment.attachments.map(attach => <Reply key={attach.text} attachment={attach} timeFormat={timeFormat} />)} {attachment.attachments && attachment.attachments.map(attach => <Reply key={attach.text} attachment={attach} timeFormat={timeFormat} />)}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
); );

View File

@ -4,7 +4,7 @@ import { View, TouchableHighlight, Text, TouchableOpacity, Vibration, ViewPropTy
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import moment from 'moment'; import moment from 'moment';
import equal from 'deep-equal'; // import equal from 'deep-equal';
import { KeyboardUtils } from 'react-native-keyboard-input'; import { KeyboardUtils } from 'react-native-keyboard-input';
import { actionsShow, errorActionsShow, toggleReactionPicker } from '../../actions/messages'; import { actionsShow, errorActionsShow, toggleReactionPicker } from '../../actions/messages';
@ -52,22 +52,16 @@ export default class Message extends React.Component {
this.state = { reactionsModal: false }; this.state = { reactionsModal: false };
this.onClose = this.onClose.bind(this); this.onClose = this.onClose.bind(this);
} }
componentWillReceiveProps() {
this.extraStyle = this.extraStyle || {};
if (this.props.item.status === messageStatus.TEMP || this.props.item.status === messageStatus.ERROR) {
this.extraStyle.opacity = 0.3;
}
}
shouldComponentUpdate(nextProps, nextState) { // shouldComponentUpdate(nextProps, nextState) {
if (!equal(this.props.reactions, nextProps.reactions)) { // if (!equal(this.props.reactions, nextProps.reactions)) {
return true; // return true;
} // }
if (this.state.reactionsModal !== nextState.reactionsModal) { // if (this.state.reactionsModal !== nextState.reactionsModal) {
return true; // return true;
} // }
return this.props.item._updatedAt.toGMTString() !== nextProps.item._updatedAt.toGMTString() || this.props.item.status !== nextProps.item.status; // return this.props.item._updatedAt.toGMTString() !== nextProps.item._updatedAt.toGMTString() || this.props.item.status !== nextProps.item.status;
} // }
onPress = () => { onPress = () => {
KeyboardUtils.dismiss(); KeyboardUtils.dismiss();
@ -129,6 +123,10 @@ export default class Message extends React.Component {
return this.props.item.t === 'rm'; return this.props.item.t === 'rm';
} }
isTemp() {
return this.props.item.status === messageStatus.TEMP || this.props.item.status === messageStatus.ERROR;
}
hasError() { hasError() {
return this.props.item.status === messageStatus.ERROR; return this.props.item.status === messageStatus.ERROR;
} }
@ -241,7 +239,7 @@ export default class Message extends React.Component {
> >
<View style={styles.flex}> <View style={styles.flex}>
{this.renderError()} {this.renderError()}
<View style={[this.extraStyle, styles.flex]}> <View style={[this.isTemp() && { opacity: 0.3 }, styles.flex]}>
<Avatar <Avatar
style={styles.avatar} style={styles.avatar}
text={item.avatar ? '' : username} text={item.avatar ? '' : username}

View File

@ -10,6 +10,8 @@ import SelectUsersView from '../../views/SelectUsersView';
import NewServerView from '../../views/NewServerView'; import NewServerView from '../../views/NewServerView';
import StarredMessagesView from '../../views/StarredMessagesView'; import StarredMessagesView from '../../views/StarredMessagesView';
import PinnedMessagesView from '../../views/PinnedMessagesView'; import PinnedMessagesView from '../../views/PinnedMessagesView';
import MentionedMessagesView from '../../views/MentionedMessagesView';
import RoomMembersView from '../../views/RoomMembersView';
const AuthRoutes = StackNavigator( const AuthRoutes = StackNavigator(
{ {
@ -57,6 +59,20 @@ const AuthRoutes = StackNavigator(
title: 'Pinned Messages', title: 'Pinned Messages',
headerTintColor: '#292E35' headerTintColor: '#292E35'
} }
},
MentionedMessages: {
screen: MentionedMessagesView,
navigationOptions: {
title: 'Mentioned Messages',
headerTintColor: '#292E35'
}
},
RoomMembers: {
screen: RoomMembersView,
navigationOptions: {
title: 'Room Members',
headerTintColor: '#292E35'
}
} }
}, },
{ {

View File

@ -28,6 +28,7 @@ const settingsSchema = {
const permissionsRolesSchema = { const permissionsRolesSchema = {
name: 'permissionsRoles', name: 'permissionsRoles',
primaryKey: 'value',
properties: { properties: {
value: 'string' value: 'string'
} }
@ -56,6 +57,7 @@ const roomsSchema = {
const subscriptionRolesSchema = { const subscriptionRolesSchema = {
name: 'subscriptionRolesSchema', name: 'subscriptionRolesSchema',
primaryKey: 'value',
properties: { properties: {
value: 'string' value: 'string'
} }
@ -70,9 +72,9 @@ const subscriptionSchema = {
t: 'string', t: 'string',
ts: { type: 'date', optional: true }, ts: { type: 'date', optional: true },
ls: { type: 'date', optional: true }, ls: { type: 'date', optional: true },
name: 'string', name: { type: 'string', indexed: true },
fname: { type: 'string', optional: true }, fname: { type: 'string', optional: true },
rid: 'string', rid: { type: 'string', indexed: true },
open: { type: 'bool', optional: true }, open: { type: 'bool', optional: true },
alert: { type: 'bool', optional: true }, alert: { type: 'bool', optional: true },
roles: { type: 'list', objectType: 'subscriptionRolesSchema' }, roles: { type: 'list', objectType: 'subscriptionRolesSchema' },
@ -83,7 +85,10 @@ const subscriptionSchema = {
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 },
lastMessage: { type: 'messages', optional: true } lastMessage: { type: 'messages', optional: true },
description: { type: 'string', optional: true },
announcement: { type: 'string', optional: true },
topic: { type: 'string', optional: true }
} }
}; };
@ -136,6 +141,7 @@ const attachment = {
const url = { const url = {
name: 'url', name: 'url',
primaryKey: 'url',
properties: { properties: {
// _id: { type: 'int', optional: true }, // _id: { type: 'int', optional: true },
url: { type: 'string', optional: true }, url: { type: 'string', optional: true },
@ -147,6 +153,7 @@ const url = {
const messagesReactionsUsernamesSchema = { const messagesReactionsUsernamesSchema = {
name: 'messagesReactionsUsernames', name: 'messagesReactionsUsernames',
primaryKey: 'value',
properties: { properties: {
value: 'string' value: 'string'
} }
@ -163,6 +170,7 @@ const messagesReactionsSchema = {
const messagesEditedBySchema = { const messagesEditedBySchema = {
name: 'messagesEditedBy', name: 'messagesEditedBy',
primaryKey: '_id',
properties: { properties: {
_id: { type: 'string', optional: true }, _id: { type: 'string', optional: true },
username: { type: 'string', optional: true } username: { type: 'string', optional: true }
@ -176,7 +184,7 @@ const messagesSchema = {
_id: 'string', _id: 'string',
msg: { type: 'string', optional: true }, msg: { type: 'string', optional: true },
t: { type: 'string', optional: true }, t: { type: 'string', optional: true },
rid: 'string', rid: { type: 'string', indexed: true },
ts: 'date', ts: 'date',
u: 'users', u: 'users',
// mentions: [], // mentions: [],
@ -209,6 +217,7 @@ const frequentlyUsedEmojiSchema = {
const customEmojiAliasesSchema = { const customEmojiAliasesSchema = {
name: 'customEmojiAliases', name: 'customEmojiAliases',
primaryKey: 'value',
properties: { properties: {
value: 'string' value: 'string'
} }

View File

@ -13,8 +13,9 @@ 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 { requestActiveUser } from '../actions/activeUsers';
import { starredMessageReceived, starredMessageUnstarred } from '../actions/starredMessages'; import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
import { pinnedMessageReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages'; import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
import Ddp from './ddp'; import Ddp from './ddp';
export { Accounts } from 'react-native-meteor'; export { Accounts } from 'react-native-meteor';
@ -64,7 +65,7 @@ const RocketChat = {
const status = (ddpMessage.fields && ddpMessage.fields.status) || 'offline'; const status = (ddpMessage.fields && ddpMessage.fields.status) || 'offline';
if (user && user.id === ddpMessage.id) { if (user && user.id === ddpMessage.id) {
return reduxStore.dispatch(setUser({ status })); reduxStore.dispatch(setUser({ status }));
} }
if (this._setUserTimer) { if (this._setUserTimer) {
@ -153,25 +154,74 @@ const RocketChat = {
this.ddp.on('rocketchat_starred_message', (ddpMessage) => { this.ddp.on('rocketchat_starred_message', (ddpMessage) => {
if (ddpMessage.msg === 'added') { if (ddpMessage.msg === 'added') {
this.starredMessages = this.starredMessages || [];
if (this.starredMessagesTimer) {
clearTimeout(this.starredMessagesTimer);
this.starredMessagesTimer = null;
}
this.starredMessagesTimer = setTimeout(() => {
reduxStore.dispatch(starredMessagesReceived(this.starredMessages));
this.starredMessagesTimer = null;
return this.starredMessages = [];
}, 1000);
const message = ddpMessage.fields; const message = ddpMessage.fields;
message._id = ddpMessage.id; message._id = ddpMessage.id;
const starredMessage = this._buildMessage(message); const starredMessage = this._buildMessage(message);
return reduxStore.dispatch(starredMessageReceived(starredMessage)); this.starredMessages = [...this.starredMessages, starredMessage];
} }
if (ddpMessage.msg === 'removed') { if (ddpMessage.msg === 'removed') {
return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id)); if (reduxStore.getState().starredMessages.isOpen) {
return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id));
}
} }
}); });
this.ddp.on('rocketchat_pinned_message', (ddpMessage) => { this.ddp.on('rocketchat_pinned_message', (ddpMessage) => {
if (ddpMessage.msg === 'added') { if (ddpMessage.msg === 'added') {
this.pinnedMessages = this.pinnedMessages || [];
if (this.pinnedMessagesTimer) {
clearTimeout(this.pinnedMessagesTimer);
this.pinnedMessagesTimer = null;
}
this.pinnedMessagesTimer = setTimeout(() => {
reduxStore.dispatch(pinnedMessagesReceived(this.pinnedMessages));
this.pinnedMessagesTimer = null;
return this.pinnedMessages = [];
}, 1000);
const message = ddpMessage.fields; const message = ddpMessage.fields;
message._id = ddpMessage.id; message._id = ddpMessage.id;
const pinnedMessage = this._buildMessage(message); const pinnedMessage = this._buildMessage(message);
return reduxStore.dispatch(pinnedMessageReceived(pinnedMessage)); this.pinnedMessages = [...this.pinnedMessages, pinnedMessage];
} }
if (ddpMessage.msg === 'removed') { if (ddpMessage.msg === 'removed') {
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id)); if (reduxStore.getState().pinnedMessages.isOpen) {
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id));
}
}
});
this.ddp.on('rocketchat_mentioned_message', (ddpMessage) => {
if (ddpMessage.msg === 'added') {
this.mentionedMessages = this.mentionedMessages || [];
if (this.mentionedMessagesTimer) {
clearTimeout(this.mentionedMessagesTimer);
this.mentionedMessagesTimer = null;
}
this.mentionedMessagesTimer = setTimeout(() => {
reduxStore.dispatch(mentionedMessagesReceived(this.mentionedMessages));
this.mentionedMessagesTimer = null;
return this.mentionedMessages = [];
}, 1000);
const message = ddpMessage.fields;
message._id = ddpMessage.id;
const mentionedMessage = this._buildMessage(message);
this.mentionedMessages = [...this.mentionedMessages, mentionedMessage];
} }
}); });
@ -324,6 +374,7 @@ const RocketChat = {
message.status = messagesStatus.SENT; message.status = messagesStatus.SENT;
normalizeMessage(message); normalizeMessage(message);
message.urls = message.urls ? RocketChat._parseUrls(message.urls) : []; message.urls = message.urls ? RocketChat._parseUrls(message.urls) : [];
message._updatedAt = new Date();
// loadHistory returns message.starred as object // loadHistory returns message.starred as object
// stream-room-messages returns message.starred as an array // stream-room-messages returns message.starred as an array
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred); message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred);
@ -497,6 +548,9 @@ const RocketChat = {
subscription.roomUpdatedAt = room._updatedAt; subscription.roomUpdatedAt = room._updatedAt;
subscription.lastMessage = normalizeMessage(room.lastMessage); subscription.lastMessage = normalizeMessage(room.lastMessage);
subscription.ro = room.ro; subscription.ro = room.ro;
subscription.description = room.description;
subscription.topic = room.topic;
subscription.announcement = room.announcement;
} }
if (subscription.roles) { if (subscription.roles) {
subscription.roles = subscription.roles.map(role => ({ value: role })); subscription.roles = subscription.roles.map(role => ({ value: role }));
@ -667,6 +721,9 @@ const RocketChat = {
}, },
toggleFavorite(rid, f) { toggleFavorite(rid, f) {
return call('toggleFavorite', rid, !f); return call('toggleFavorite', rid, !f);
},
getRoomMembers(rid, allUsers) {
return call('getUsersOfRoom', rid, allUsers);
} }
}; };

View File

@ -83,6 +83,9 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
}, },
updateAlert: {
color: '#1d74f5'
},
status: { status: {
position: 'absolute', position: 'absolute',
bottom: -3, bottom: -3,
@ -173,7 +176,7 @@ export default class RoomItem extends React.PureComponent {
get lastMessage() { get lastMessage() {
const { const {
lastMessage, alert, type lastMessage, type
} = this.props; } = this.props;
if (!this.props.StoreLastMessage) { if (!this.props.StoreLastMessage) {
@ -183,7 +186,6 @@ export default class RoomItem extends React.PureComponent {
return 'No Message'; return 'No Message';
} }
let prefix = ''; let prefix = '';
if (lastMessage.u.username === this.props.user.username) { if (lastMessage.u.username === this.props.user.username) {
@ -193,13 +195,7 @@ export default class RoomItem extends React.PureComponent {
} }
const msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`; const msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`;
const maxChars = 35; const maxChars = 35;
if (alert) {
return `**${ msg.slice(0, maxChars) }${ msg.replace(/:[a-z0-9]+:/gi, ':::').length > maxChars ? '...' : '' }**`;
}
return `${ msg.slice(0, maxChars) }${ msg.replace(/:[a-z0-9]+:/gi, ':::').length > maxChars ? '...' : '' }`; return `${ msg.slice(0, maxChars) }${ msg.replace(/:[a-z0-9]+:/gi, ':::').length > maxChars ? '...' : '' }`;
} }
@ -237,7 +233,7 @@ export default class RoomItem extends React.PureComponent {
<View style={styles.roomNameView}> <View style={styles.roomNameView}>
<View style={styles.firstRow}> <View style={styles.firstRow}>
<Text style={[styles.roomName, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text> <Text style={[styles.roomName, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
{_updatedAt ? <Text style={styles.update} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null} {_updatedAt ? <Text style={[styles.update, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
</View> </View>
<View style={styles.row}> <View style={styles.row}>
<Markdown <Markdown

View File

@ -14,6 +14,7 @@ import customEmojis from './customEmojis';
import activeUsers from './activeUsers'; import activeUsers from './activeUsers';
import starredMessages from './starredMessages'; import starredMessages from './starredMessages';
import pinnedMessages from './pinnedMessages'; import pinnedMessages from './pinnedMessages';
import mentionedMessages from './mentionedMessages';
export default combineReducers({ export default combineReducers({
settings, settings,
@ -30,5 +31,6 @@ export default combineReducers({
customEmojis, customEmojis,
activeUsers, activeUsers,
starredMessages, starredMessages,
pinnedMessages pinnedMessages,
mentionedMessages
}); });

View File

@ -0,0 +1,19 @@
import { MENTIONED_MESSAGES } from '../actions/actionsTypes';
const initialState = {
messages: []
};
export default function server(state = initialState, action) {
switch (action.type) {
case MENTIONED_MESSAGES.MESSAGES_RECEIVED:
return {
...state,
messages: [...state.messages, ...action.messages]
};
case MENTIONED_MESSAGES.CLOSE:
return initialState;
default:
return state;
}
}

View File

@ -1,15 +1,21 @@
import { PINNED_MESSAGES } from '../actions/actionsTypes'; import { PINNED_MESSAGES } from '../actions/actionsTypes';
const initialState = { const initialState = {
messages: [] messages: [],
isOpen: false
}; };
export default function server(state = initialState, action) { export default function server(state = initialState, action) {
switch (action.type) { switch (action.type) {
case PINNED_MESSAGES.MESSAGE_RECEIVED: case PINNED_MESSAGES.OPEN:
return { return {
...state, ...state,
messages: [...state.messages, action.message] isOpen: true
};
case PINNED_MESSAGES.MESSAGES_RECEIVED:
return {
...state,
messages: [...state.messages, ...action.messages]
}; };
case PINNED_MESSAGES.MESSAGE_UNPINNED: case PINNED_MESSAGES.MESSAGE_UNPINNED:
return { return {

View File

@ -1,15 +1,21 @@
import { STARRED_MESSAGES } from '../actions/actionsTypes'; import { STARRED_MESSAGES } from '../actions/actionsTypes';
const initialState = { const initialState = {
messages: [] messages: [],
isOpen: false
}; };
export default function server(state = initialState, action) { export default function server(state = initialState, action) {
switch (action.type) { switch (action.type) {
case STARRED_MESSAGES.MESSAGE_RECEIVED: case STARRED_MESSAGES.OPEN:
return { return {
...state, ...state,
messages: [...state.messages, action.message] isOpen: true
};
case STARRED_MESSAGES.MESSAGES_RECEIVED:
return {
...state,
messages: [...state.messages, ...action.messages]
}; };
case STARRED_MESSAGES.MESSAGE_UNSTARRED: case STARRED_MESSAGES.MESSAGE_UNSTARRED:
return { return {

View File

@ -11,6 +11,7 @@ import state from './state';
import activeUsers from './activeUsers'; import activeUsers from './activeUsers';
import starredMessages from './starredMessages'; import starredMessages from './starredMessages';
import pinnedMessages from './pinnedMessages'; import pinnedMessages from './pinnedMessages';
import mentionedMessages from './mentionedMessages';
const root = function* root() { const root = function* root() {
yield all([ yield all([
@ -25,7 +26,8 @@ const root = function* root() {
state(), state(),
activeUsers(), activeUsers(),
starredMessages(), starredMessages(),
pinnedMessages() pinnedMessages(),
mentionedMessages()
]); ]);
}; };

View File

@ -0,0 +1,14 @@
import { take, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
const watchMentionedMessagesRoom = function* watchMentionedMessagesRoom({ rid }) {
const sub = yield RocketChat.subscribe('mentionedMessages', rid, 50);
yield take(types.MENTIONED_MESSAGES.CLOSE);
sub.unsubscribe().catch(e => alert(e));
};
const root = function* root() {
yield takeLatest(types.MENTIONED_MESSAGES.OPEN, watchMentionedMessagesRoom);
};
export default root;

View File

@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { openMentionedMessages, closeMentionedMessages } from '../../actions/mentionedMessages';
import styles from './styles';
import Message from '../../containers/message';
@connect(
state => ({
messages: state.mentionedMessages.messages,
user: state.login.user,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}),
dispatch => ({
openMentionedMessages: rid => dispatch(openMentionedMessages(rid)),
closeMentionedMessages: () => dispatch(closeMentionedMessages())
})
)
export default class MentionedMessagesView extends React.PureComponent {
static propTypes = {
navigation: PropTypes.object,
messages: PropTypes.array,
user: PropTypes.object,
baseUrl: PropTypes.string,
openMentionedMessages: PropTypes.func,
closeMentionedMessages: PropTypes.func
}
componentDidMount() {
this.props.openMentionedMessages(this.props.navigation.state.params.rid);
}
componentWillUnmount() {
this.props.closeMentionedMessages();
}
renderEmpty = () => (
<View style={styles.listEmptyContainer}>
<Text>No mentioned messages</Text>
</View>
)
renderItem = ({ item }) => (
<Message
item={item}
style={styles.message}
reactions={item.reactions}
user={this.props.user}
baseUrl={this.props.baseUrl}
Message_TimeFormat='MMMM Do YYYY, h:mm:ss a'
onLongPress={() => {}}
/>
)
render() {
if (this.props.messages.length === 0) {
return this.renderEmpty();
}
return (
<FlatList
key='mentioned-messages-view-list'
data={this.props.messages}
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
/>
);
}
}

View File

@ -0,0 +1,17 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
list: {
flex: 1,
backgroundColor: '#ffffff'
},
message: {
transform: [{ scaleY: 1 }]
},
listEmptyContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ffffff'
}
});

View File

@ -43,7 +43,7 @@ export default class PinnedMessagesView extends React.PureComponent {
}; };
} }
componentWillMount() { componentDidMount() {
this.props.openPinnedMessages(this.props.navigation.state.params.rid); this.props.openPinnedMessages(this.props.navigation.state.params.rid);
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, SectionList, Text, StyleSheet } from 'react-native'; import { View, SectionList, Text } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons'; import Icon from 'react-native-vector-icons/Ionicons';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -9,6 +9,7 @@ import styles from './styles';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
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';
@connect(state => ({ @connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
@ -29,25 +30,19 @@ export default class RoomActionsView extends React.PureComponent {
}; };
} }
componentWillMount() { componentDidMount() {
this.updateRoom(); this.updateRoom();
this.updateSections(); this.updateSections();
}
componentDidMount() {
this.rooms.addListener(this.updateRoom); this.rooms.addListener(this.updateRoom);
} }
updateRoom = () => { updateRoom = () => {
const [room] = this.rooms; const [room] = this.rooms;
this.setState({ room }); this.setState({ room });
this.props.navigation.setParams({
f: room.f
});
this.updateSections(); this.updateSections();
} }
updateSections = () => { updateSections = async() => {
const { rid, t } = this.state.room; const { rid, t } = this.state.room;
const sections = [{ const sections = [{
data: [{ icon: 'ios-star', name: 'USER' }], data: [{ icon: 'ios-star', name: 'USER' }],
@ -61,7 +56,12 @@ export default class RoomActionsView extends React.PureComponent {
}, { }, {
data: [ data: [
{ icon: 'ios-attach', name: 'Files' }, { icon: 'ios-attach', name: 'Files' },
{ icon: 'ios-at-outline', name: 'Mentions' }, {
icon: 'ios-at-outline',
name: 'Mentions',
route: 'MentionedMessages',
params: { rid }
},
{ {
icon: 'ios-star-outline', icon: 'ios-star-outline',
name: 'Starred', name: 'Starred',
@ -90,7 +90,16 @@ export default class RoomActionsView extends React.PureComponent {
renderItem: this.renderItem renderItem: this.renderItem
}); });
} else if (t === 'c' || t === 'p') { } else if (t === 'c' || t === 'p') {
sections[2].data.unshift({ icon: 'ios-people', name: 'Members', description: '42 members' }); const membersResult = await RocketChat.getRoomMembers(rid, false);
const members = membersResult.records;
sections[2].data.unshift({
icon: 'ios-people',
name: 'Members',
description: (members.length === 1 ? `${ members.length } member` : `${ members.length } members`),
route: 'RoomMembers',
params: { rid, members }
});
sections.push({ sections.push({
data: [ data: [
{ icon: 'ios-volume-off', name: 'Mute channel' }, { icon: 'ios-volume-off', name: 'Mute channel' },
@ -102,21 +111,28 @@ export default class RoomActionsView extends React.PureComponent {
this.setState({ sections }); this.setState({ sections });
} }
renderRoomInfo = ({ item }) => this.renderTouchableItem([ renderRoomInfo = ({ item }) => {
<Avatar const {
key='avatar' fname, name, t, topic
text={this.state.room.name} } = this.state.room;
size={50} return (
style={StyleSheet.flatten(styles.avatar)} this.renderTouchableItem([
baseUrl={this.props.baseUrl} <Avatar
type={this.state.room.t} key='avatar'
/>, text={name}
<View key='name' style={styles.roomTitleContainer}> size={50}
<Text style={styles.roomTitle}>{this.state.room.fname}</Text> style={styles.avatar}
<Text style={styles.roomDescription}>@{this.state.room.name}</Text> baseUrl={this.props.baseUrl}
</View>, type={t}
<Icon key='icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#cbced1' /> />,
], item) <View key='name' style={styles.roomTitleContainer}>
<Text style={styles.roomTitle}>{t === 'd' ? fname : name}</Text>
<Text style={styles.roomDescription} ellipsizeMode='tail' numberOfLines={1}>{t === 'd' ? `@${ name }` : topic}</Text>
</View>,
<Icon key='icon' name='ios-arrow-forward' size={20} style={styles.sectionItemIcon} color='#cbced1' />
], item)
);
}
renderTouchableItem = (subview, item) => ( renderTouchableItem = (subview, item) => (
<Touch <Touch

View File

@ -0,0 +1,139 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, Text, View, TextInput } from 'react-native';
import { connect } from 'react-redux';
import styles from './styles';
import Avatar from '../../containers/Avatar';
import Status from '../../containers/status';
import Touch from '../../utils/touch';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import RocketChat from '../../lib/rocketchat';
import { goRoom } from '../../containers/routes/NavigationService';
import database from '../../lib/realm';
@connect(state => ({
user: state.login.user,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}))
export default class MentionedMessagesView extends React.PureComponent {
static propTypes = {
navigation: PropTypes.object
}
static navigationOptions = ({ navigation }) => {
const params = navigation.state.params || {};
const label = params.allUsers ? 'All' : 'Online';
if (params.allUsers === undefined) {
return;
}
return {
headerRight: (
<Touch
onPress={params.onPressToogleStatus}
underlayColor='#ffffff'
activeOpacity={0.5}
accessibilityLabel={label}
accessibilityTraits='button'
style={styles.headerButtonTouchable}
>
<View style={styles.headerButton}>
<Text style={styles.headerButtonText}>{label}</Text>
</View>
</Touch>
)
};
};
constructor(props) {
super(props);
const { rid, members } = props.navigation.state.params;
this.state = {
allUsers: false,
filtering: false,
rid,
members,
membersFiltered: []
};
}
componentWillMount() {
this.props.navigation.setParams({
onPressToogleStatus: this.onPressToogleStatus,
allUsers: this.state.allUsers
});
}
onSearchChangeText = (text) => {
let membersFiltered = [];
if (text) {
membersFiltered = this.state.members.filter(m => m.username.toLowerCase().match(text.toLowerCase()));
}
this.setState({ filtering: !!text, membersFiltered });
}
onPressToogleStatus = async() => {
const allUsers = !this.state.allUsers;
this.props.navigation.setParams({ allUsers });
const membersResult = await RocketChat.getRoomMembers(this.state.rid, allUsers);
const members = membersResult.records;
this.setState({ allUsers, members });
}
onPressItem = async(item) => {
const subscriptions = database.objects('subscriptions').filtered('name = $0', item.username);
if (subscriptions.length) {
goRoom({ rid: subscriptions[0].rid, name: subscriptions[0].name });
} else {
const room = await RocketChat.createDirectMessage(item.username);
goRoom({ room: room.rid, name: item.username });
}
}
renderSearchBar = () => (
<View style={styles.searchBoxView}>
<TextInput
underlineColorAndroid='transparent'
style={styles.searchBox}
onChangeText={text => this.onSearchChangeText(text)}
returnKeyType='search'
placeholder='Search'
clearButtonMode='while-editing'
blurOnSubmit
/>
</View>
)
renderSeparator = () => <View style={styles.separator} />;
renderItem = ({ item }) => (
<Touch
onPress={() => this.onPressItem(item)}
underlayColor='#ffffff'
activeOpacity={0.5}
accessibilityLabel={`Start a conversation with ${ item.username }`}
accessibilityTraits='button'
>
<View style={styles.item}>
<Avatar text={item.username} size={30} type='d' style={styles.avatar}>{<Status style={styles.status} id={item._id} />}</Avatar>
<Text style={styles.username}>{item.username}</Text>
</View>
</Touch>
)
render() {
const { filtering, members, membersFiltered } = this.state;
return (
<FlatList
key='room-members-view-list'
data={filtering ? membersFiltered : members}
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderSearchBar}
{...scrollPersistTaps}
/>
);
}
}

View File

@ -0,0 +1,59 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
list: {
flex: 1,
backgroundColor: '#ffffff'
},
item: {
flexDirection: 'row',
paddingVertical: 8,
paddingHorizontal: 16,
alignItems: 'center'
},
avatar: {
marginRight: 16
},
status: {
position: 'absolute',
bottom: -3,
right: -3,
borderWidth: 2,
borderColor: '#fff',
borderRadius: 12,
width: 12,
height: 12
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: '#ddd'
},
username: {
flex: 1,
fontSize: 16,
color: '#444'
},
headerButtonTouchable: {
borderRadius: 4
},
headerButton: {
padding: 6,
backgroundColor: 'transparent',
alignItems: 'center',
justifyContent: 'center'
},
headerButtonText: {
color: '#292E35'
},
searchBoxView: {
backgroundColor: '#eee'
},
searchBox: {
backgroundColor: '#fff',
margin: 5,
borderRadius: 5,
padding: 5,
paddingLeft: 10,
color: '#aaa'
}
});

View File

@ -27,7 +27,7 @@ export class DataSource extends OldList.DataSource {
} }
} }
const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id }); const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id || r1._updatedAt.toISOString() !== r2._updatedAt.toISOString() });
export class List extends React.Component { export class List extends React.Component {
static propTypes = { static propTypes = {

View File

@ -78,7 +78,7 @@ export default class RoomView extends React.Component {
this.onReactionPress = this.onReactionPress.bind(this); this.onReactionPress = this.onReactionPress.bind(this);
} }
async componentWillMount() { async componentDidMount() {
this.props.navigation.setParams({ this.props.navigation.setParams({
title: this.name title: this.name
}); });

View File

@ -43,7 +43,7 @@ export default class StarredMessagesView extends React.PureComponent {
}; };
} }
componentWillMount() { componentDidMount() {
this.props.openStarredMessages(this.props.navigation.state.params.rid); this.props.openStarredMessages(this.props.navigation.state.params.rid);
} }