Use Rest API pt 2 (#568)

* Room files

* Pinned messages

* Starred messages

* Mentioned messages

* Search messages

* Bug fixes

* Profile

* Livechat

* Block/unblock user

* Erase room

* Archive room

* Remove unused method

* Bug fix
This commit is contained in:
Diego Mello 2018-12-12 13:15:10 -02:00 committed by GitHub
parent dca2181b2c
commit ad37586065
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 531 additions and 998 deletions

View File

@ -77,10 +77,6 @@ export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DI
export const LOGOUT = 'LOGOUT'; // logout is always success export const LOGOUT = 'LOGOUT'; // logout is always success
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET']); export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET']);
export const ROLES = createRequestTypes('ROLES', ['SET']); export const ROLES = createRequestTypes('ROLES', ['SET']);
export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNSTARRED']);
export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']);
export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']); export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
export const ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']); export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);

View File

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

View File

@ -1,36 +0,0 @@
import * as types from './actionsTypes';
export function openPinnedMessages(rid, limit) {
return {
type: types.PINNED_MESSAGES.OPEN,
rid,
limit
};
}
export function readyPinnedMessages() {
return {
type: types.PINNED_MESSAGES.READY
};
}
export function closePinnedMessages() {
return {
type: types.PINNED_MESSAGES.CLOSE
};
}
export function pinnedMessagesReceived(messages) {
return {
type: types.PINNED_MESSAGES.MESSAGES_RECEIVED,
messages
};
}
export function pinnedMessageUnpinned(messageId) {
return {
type: types.PINNED_MESSAGES.MESSAGE_UNPINNED,
messageId
};
}

View File

@ -1,28 +0,0 @@
import * as types from './actionsTypes';
export function openRoomFiles(rid, limit) {
return {
type: types.ROOM_FILES.OPEN,
rid,
limit
};
}
export function readyRoomFiles() {
return {
type: types.ROOM_FILES.READY
};
}
export function closeRoomFiles() {
return {
type: types.ROOM_FILES.CLOSE
};
}
export function roomFilesReceived(messages) {
return {
type: types.ROOM_FILES.MESSAGES_RECEIVED,
messages
};
}

View File

@ -1,35 +0,0 @@
import * as types from './actionsTypes';
export function openStarredMessages(rid, limit) {
return {
type: types.STARRED_MESSAGES.OPEN,
rid,
limit
};
}
export function readyStarredMessages() {
return {
type: types.STARRED_MESSAGES.READY
};
}
export function closeStarredMessages() {
return {
type: types.STARRED_MESSAGES.CLOSE
};
}
export function starredMessagesReceived(messages) {
return {
type: types.STARRED_MESSAGES.MESSAGES_RECEIVED,
messages
};
}
export function starredMessageUnstarred(messageId) {
return {
type: types.STARRED_MESSAGES.MESSAGE_UNSTARRED,
messageId
};
}

View File

@ -39,7 +39,8 @@ const SYSTEM_MESSAGES = [
'room_changed_description', 'room_changed_description',
'room_changed_announcement', 'room_changed_announcement',
'room_changed_topic', 'room_changed_topic',
'room_changed_privacy' 'room_changed_privacy',
'message_snippeted'
]; ];
const getInfoMessage = ({ const getInfoMessage = ({
@ -76,6 +77,8 @@ const getInfoMessage = ({
return I18n.t('Room_changed_topic', { topic: msg, userBy: username }); return I18n.t('Room_changed_topic', { topic: msg, userBy: username });
} else if (type === 'room_changed_privacy') { } else if (type === 'room_changed_privacy') {
return I18n.t('Room_changed_privacy', { type: msg, userBy: username }); return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
} else if (type === 'message_snippeted') {
return I18n.t('Created_snippet');
} }
return ''; return '';
}; };
@ -107,7 +110,10 @@ export default class Message extends PureComponent {
header: PropTypes.bool, header: PropTypes.bool,
avatar: PropTypes.string, avatar: PropTypes.string,
alias: PropTypes.string, alias: PropTypes.string,
ts: PropTypes.instanceOf(Date), ts: PropTypes.oneOfType([
PropTypes.instanceOf(Date),
PropTypes.string
]),
edited: PropTypes.bool, edited: PropTypes.bool,
attachments: PropTypes.oneOfType([ attachments: PropTypes.oneOfType([
PropTypes.array, PropTypes.array,

View File

@ -35,7 +35,10 @@ export default class User extends React.PureComponent {
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
username: PropTypes.string, username: PropTypes.string,
alias: PropTypes.string, alias: PropTypes.string,
ts: PropTypes.instanceOf(Date), ts: PropTypes.oneOfType([
PropTypes.instanceOf(Date),
PropTypes.string
]),
temp: PropTypes.bool temp: PropTypes.bool
} }

View File

@ -131,6 +131,7 @@ export default {
Copy_Permalink: 'Copy Permalink', Copy_Permalink: 'Copy Permalink',
Create_account: 'Create an account', Create_account: 'Create an account',
Create_Channel: 'Create Channel', Create_Channel: 'Create Channel',
Created_snippet: 'Created a snippet',
Create_a_new_workspace: 'Create a new workspace', Create_a_new_workspace: 'Create a new workspace',
Create: 'Create', Create: 'Create',
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.', Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
@ -209,6 +210,7 @@ export default {
No_files: 'No files', No_files: 'No files',
No_mentioned_messages: 'No mentioned messages', No_mentioned_messages: 'No mentioned messages',
No_pinned_messages: 'No pinned messages', No_pinned_messages: 'No pinned messages',
No_results_found: 'No results found',
No_snippeted_messages: 'No snippeted messages', No_snippeted_messages: 'No snippeted messages',
No_starred_messages: 'No starred messages', No_starred_messages: 'No starred messages',
No_announcement_provided: 'No announcement provided.', No_announcement_provided: 'No announcement provided.',

View File

@ -138,6 +138,7 @@ export default {
Copy_Permalink: 'Copiar Link-Permanente', Copy_Permalink: 'Copiar Link-Permanente',
Create_account: 'Criar conta', Create_account: 'Criar conta',
Create_Channel: 'Criar Canal', Create_Channel: 'Criar Canal',
Created_snippet: 'Criou um snippet',
Create_a_new_workspace: 'Criar nova área de trabalho', Create_a_new_workspace: 'Criar nova área de trabalho',
Create: 'Criar', Create: 'Criar',
Delete_Room_Warning: 'A exclusão de uma sala irá apagar todas as mensagens postadas na sala. Isso não pode ser desfeito.', Delete_Room_Warning: 'A exclusão de uma sala irá apagar todas as mensagens postadas na sala. Isso não pode ser desfeito.',
@ -212,6 +213,7 @@ export default {
No_files: 'Não há arquivos', No_files: 'Não há arquivos',
No_mentioned_messages: 'Não há menções', No_mentioned_messages: 'Não há menções',
No_pinned_messages: 'Não há mensagens fixadas', No_pinned_messages: 'Não há mensagens fixadas',
No_results_found: 'Nenhum resultado encontrado',
No_snippeted_messages: 'Não há trechos de mensagens', No_snippeted_messages: 'Não há trechos de mensagens',
No_starred_messages: 'Não há mensagens favoritas', No_starred_messages: 'Não há mensagens favoritas',
No_announcement_provided: 'Sem anúncio.', No_announcement_provided: 'Sem anúncio.',

View File

@ -6,6 +6,19 @@ import database from '../realm';
import log from '../../utils/log'; import log from '../../utils/log';
async function load({ rid: roomId, latest, t }) { async function load({ rid: roomId, latest, t }) {
if (t === 'l') {
try {
const data = await SDK.driver.asyncCall('loadHistory', roomId, null, 50, latest);
if (!data || data.status === 'error') {
return [];
}
return data.messages;
} catch (error) {
console.log(error);
return [];
}
}
let params = { roomId, count: 50 }; let params = { roomId, count: 50 };
if (latest) { if (latest) {
params = { ...params, latest: new Date(latest).toISOString() }; params = { ...params, latest: new Date(latest).toISOString() };

View File

@ -14,11 +14,7 @@ import {
} from '../actions/login'; } from '../actions/login';
import { disconnect, connectSuccess, connectRequest } from '../actions/connect'; import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
import { setActiveUser } from '../actions/activeUsers'; import { setActiveUser } from '../actions/activeUsers';
import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
import { snippetedMessagesReceived } from '../actions/snippetedMessages'; import { snippetedMessagesReceived } from '../actions/snippetedMessages';
import { roomFilesReceived } from '../actions/roomFiles';
import { someoneTyping, roomMessageReceived } from '../actions/room'; import { someoneTyping, roomMessageReceived } from '../actions/room';
import { setRoles } from '../actions/roles'; import { setRoles } from '../actions/roles';
@ -199,79 +195,6 @@ const RocketChat = {
} }
})); }));
SDK.driver.on('rocketchat_starred_message', protectedFunction((error, ddpMessage) => {
if (ddpMessage.msg === 'added') {
this.starredMessages = this.starredMessages || [];
if (this.starredMessagesTimer) {
clearTimeout(this.starredMessagesTimer);
this.starredMessagesTimer = null;
}
this.starredMessagesTimer = setTimeout(protectedFunction(() => {
reduxStore.dispatch(starredMessagesReceived(this.starredMessages));
this.starredMessagesTimer = null;
return this.starredMessages = [];
}), 1000);
const message = ddpMessage.fields;
message._id = ddpMessage.id;
const starredMessage = _buildMessage(message);
this.starredMessages = [...this.starredMessages, starredMessage];
}
if (ddpMessage.msg === 'removed') {
if (reduxStore.getState().starredMessages.isOpen) {
return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id));
}
}
}));
SDK.driver.on('rocketchat_pinned_message', protectedFunction((error, ddpMessage) => {
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;
message._id = ddpMessage.id;
const pinnedMessage = _buildMessage(message);
this.pinnedMessages = [...this.pinnedMessages, pinnedMessage];
}
if (ddpMessage.msg === 'removed') {
if (reduxStore.getState().pinnedMessages.isOpen) {
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id));
}
}
}));
SDK.driver.on('rocketchat_mentioned_message', protectedFunction((error, 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 = _buildMessage(message);
this.mentionedMessages = [...this.mentionedMessages, mentionedMessage];
}
}));
SDK.driver.on('rocketchat_snippeted_message', protectedFunction((error, ddpMessage) => { SDK.driver.on('rocketchat_snippeted_message', protectedFunction((error, ddpMessage) => {
if (ddpMessage.msg === 'added') { if (ddpMessage.msg === 'added') {
this.snippetedMessages = this.snippetedMessages || []; this.snippetedMessages = this.snippetedMessages || [];
@ -293,50 +216,6 @@ const RocketChat = {
} }
})); }));
SDK.driver.on('room_files', protectedFunction((error, ddpMessage) => {
if (ddpMessage.msg === 'added') {
this.roomFiles = this.roomFiles || [];
if (this.roomFilesTimer) {
clearTimeout(this.roomFilesTimer);
this.roomFilesTimer = null;
}
this.roomFilesTimer = setTimeout(() => {
reduxStore.dispatch(roomFilesReceived(this.roomFiles));
this.roomFilesTimer = null;
return this.roomFiles = [];
}, 1000);
const { fields } = ddpMessage;
const message = {
_id: ddpMessage.id,
ts: fields.uploadedAt,
msg: fields.description,
status: 0,
attachments: [{
title: fields.name
}],
urls: [],
reactions: [],
u: {
username: fields.user.username
}
};
const fileUrl = `/file-upload/${ ddpMessage.id }/${ fields.name }`;
if (/image/.test(fields.type)) {
message.attachments[0].image_type = fields.type;
message.attachments[0].image_url = fileUrl;
} else if (/audio/.test(fields.type)) {
message.attachments[0].audio_type = fields.type;
message.attachments[0].audio_url = fileUrl;
} else if (/video/.test(fields.type)) {
message.attachments[0].video_type = fields.type;
message.attachments[0].video_url = fileUrl;
}
this.roomFiles = [...this.roomFiles, message];
}
}));
SDK.driver.on('rocketchat_roles', protectedFunction((error, ddpMessage) => { SDK.driver.on('rocketchat_roles', protectedFunction((error, ddpMessage) => {
this.roles = this.roles || {}; this.roles = this.roles || {};
@ -678,7 +557,7 @@ const RocketChat = {
async getRoomMember(rid, currentUserId) { async getRoomMember(rid, currentUserId) {
try { try {
const membersResult = await RocketChat.getRoomMembers(rid, true); const membersResult = await RocketChat.getRoomMembers(rid, true);
return Promise.resolve(membersResult.records.find(m => m.id !== currentUserId)); return Promise.resolve(membersResult.records.find(m => m._id !== currentUserId));
} catch (error) { } catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
@ -692,8 +571,8 @@ const RocketChat = {
leaveRoom(roomId, t) { leaveRoom(roomId, t) {
return SDK.api.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId }); return SDK.api.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId });
}, },
eraseRoom(rid) { eraseRoom(roomId, t) {
return call('eraseRoom', rid); return SDK.api.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId });
}, },
toggleMuteUserInRoom(rid, username, mute) { toggleMuteUserInRoom(rid, username, mute) {
if (mute) { if (mute) {
@ -701,17 +580,17 @@ const RocketChat = {
} }
return call('unmuteUserInRoom', { rid, username }); return call('unmuteUserInRoom', { rid, username });
}, },
toggleArchiveRoom(rid, archive) { toggleArchiveRoom(roomId, t, archive) {
if (archive) { if (archive) {
return call('archiveRoom', rid); return SDK.api.post(`${ this.roomTypeToApiType(t) }.archive`, { roomId });
} }
return call('unarchiveRoom', rid); return SDK.api.post(`${ this.roomTypeToApiType(t) }.unarchive`, { roomId });
}, },
saveRoomSettings(rid, params) { saveRoomSettings(rid, params) {
return call('saveRoomSettings', rid, params); return call('saveRoomSettings', rid, params);
}, },
saveUserProfile(params, customFields) { saveUserProfile(data) {
return call('saveUserProfile', params, customFields); return SDK.api.post('users.updateOwnBasicInfo', { data });
}, },
saveUserPreferences(params) { saveUserPreferences(params) {
return call('saveUserPreferences', params); return call('saveUserPreferences', params);
@ -719,9 +598,6 @@ const RocketChat = {
saveNotificationSettings(roomId, notifications) { saveNotificationSettings(roomId, notifications) {
return SDK.api.post('rooms.saveNotification', { roomId, notifications }); return SDK.api.post('rooms.saveNotification', { roomId, notifications });
}, },
messageSearch(text, rid, limit) {
return call('messageSearch', text, rid, limit);
},
addUsersToRoom(rid) { addUsersToRoom(rid) {
let { users } = reduxStore.getState().selectedUsers; let { users } = reduxStore.getState().selectedUsers;
users = users.map(u => u.name); users = users.map(u => u.name);
@ -756,8 +632,8 @@ const RocketChat = {
getAvatarSuggestion() { getAvatarSuggestion() {
return call('getAvatarSuggestion'); return call('getAvatarSuggestion');
}, },
resetAvatar() { resetAvatar(userId) {
return call('resetAvatar'); return SDK.api.post('users.resetAvatar', { userId });
}, },
setAvatarFromService({ data, contentType = '', service = null }) { setAvatarFromService({ data, contentType = '', service = null }) {
return call('setAvatarFromService', data, contentType, service); return call('setAvatarFromService', data, contentType, service);
@ -804,6 +680,30 @@ const RocketChat = {
c: 'channels', d: 'im', p: 'groups' c: 'channels', d: 'im', p: 'groups'
}; };
return types[t]; return types[t];
},
getFiles(roomId, type, offset) {
return SDK.api.get(`${ this.roomTypeToApiType(type) }.files`, {
roomId,
offset,
sort: { uploadedAt: -1 },
fields: {
name: 1, description: 1, size: 1, type: 1, uploadedAt: 1, url: 1, userId: 1
}
});
},
getMessages(roomId, type, query, offset) {
return SDK.api.get(`${ this.roomTypeToApiType(type) }.messages`, {
roomId,
query,
offset,
sort: { ts: -1 }
});
},
searchMessages(roomId, searchText) {
return SDK.api.get('chat.search', {
roomId,
searchText
});
} }
}; };

View File

@ -12,11 +12,7 @@ import app from './app';
import customEmojis from './customEmojis'; import customEmojis from './customEmojis';
import activeUsers from './activeUsers'; import activeUsers from './activeUsers';
import roles from './roles'; import roles from './roles';
import starredMessages from './starredMessages';
import pinnedMessages from './pinnedMessages';
import mentionedMessages from './mentionedMessages';
import snippetedMessages from './snippetedMessages'; import snippetedMessages from './snippetedMessages';
import roomFiles from './roomFiles';
import sortPreferences from './sortPreferences'; import sortPreferences from './sortPreferences';
export default combineReducers({ export default combineReducers({
@ -33,10 +29,6 @@ export default combineReducers({
customEmojis, customEmojis,
activeUsers, activeUsers,
roles, roles,
starredMessages,
pinnedMessages,
mentionedMessages,
snippetedMessages, snippetedMessages,
roomFiles,
sortPreferences sortPreferences
}); });

View File

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

View File

@ -1,37 +0,0 @@
import { PINNED_MESSAGES } from '../actions/actionsTypes';
const initialState = {
messages: [],
isOpen: false,
ready: false
};
export default function server(state = initialState, action) {
switch (action.type) {
case PINNED_MESSAGES.OPEN:
return {
...state,
isOpen: true,
ready: false
};
case PINNED_MESSAGES.READY:
return {
...state,
ready: true
};
case PINNED_MESSAGES.MESSAGES_RECEIVED:
return {
...state,
messages: [...state.messages, ...action.messages]
};
case PINNED_MESSAGES.MESSAGE_UNPINNED:
return {
...state,
messages: state.messages.filter(message => message._id !== action.messageId)
};
case PINNED_MESSAGES.CLOSE:
return initialState;
default:
return state;
}
}

View File

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

View File

@ -1,37 +0,0 @@
import { STARRED_MESSAGES } from '../actions/actionsTypes';
const initialState = {
messages: [],
isOpen: false,
ready: false
};
export default function server(state = initialState, action) {
switch (action.type) {
case STARRED_MESSAGES.OPEN:
return {
...state,
isOpen: true,
ready: false
};
case STARRED_MESSAGES.READY:
return {
...state,
ready: true
};
case STARRED_MESSAGES.MESSAGES_RECEIVED:
return {
...state,
messages: [...state.messages, ...action.messages]
};
case STARRED_MESSAGES.MESSAGE_UNSTARRED:
return {
...state,
messages: state.messages.filter(message => message._id !== action.messageId)
};
case STARRED_MESSAGES.CLOSE:
return initialState;
default:
return state;
}
}

View File

@ -6,11 +6,7 @@ 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 starredMessages from './starredMessages';
import pinnedMessages from './pinnedMessages';
import mentionedMessages from './mentionedMessages';
import snippetedMessages from './snippetedMessages'; import snippetedMessages from './snippetedMessages';
import roomFiles from './roomFiles';
import deepLinking from './deepLinking'; import deepLinking from './deepLinking';
const root = function* root() { const root = function* root() {
@ -22,11 +18,7 @@ const root = function* root() {
messages(), messages(),
selectServer(), selectServer(),
state(), state(),
starredMessages(),
pinnedMessages(),
mentionedMessages(),
snippetedMessages(), snippetedMessages(),
roomFiles(),
deepLinking() deepLinking()
]); ]);
}; };

View File

@ -33,7 +33,8 @@ const handleLoginRequest = function* handleLoginRequest({ credentials }) {
username: data.me.username, username: data.me.username,
name: data.me.name, name: data.me.name,
language: data.me.language, language: data.me.language,
status: data.me.status status: data.me.status,
customFields: data.me.customFields
}; };
return yield put(loginSuccess(user)); return yield put(loginSuccess(user));
} }

View File

@ -1,41 +0,0 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyMentionedMessages } from '../actions/mentionedMessages';
import log from '../utils/log';
let sub;
let newSub;
const openMentionedMessagesRoom = function* openMentionedMessagesRoom({ rid, limit }) {
try {
newSub = yield RocketChat.subscribe('mentionedMessages', rid, limit);
yield put(readyMentionedMessages());
if (sub) {
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {
log('openMentionedMessagesRoom', e);
}
};
const closeMentionedMessagesRoom = function* closeMentionedMessagesRoom() {
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeMentionedMessagesRoom', e);
}
};
const root = function* root() {
yield takeLatest(types.MENTIONED_MESSAGES.OPEN, openMentionedMessagesRoom);
yield takeLatest(types.MENTIONED_MESSAGES.CLOSE, closeMentionedMessagesRoom);
};
export default root;

View File

@ -1,41 +0,0 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyPinnedMessages } from '../actions/pinnedMessages';
import log from '../utils/log';
let sub;
let newSub;
const openPinnedMessagesRoom = function* openPinnedMessagesRoom({ rid, limit }) {
try {
newSub = yield RocketChat.subscribe('pinnedMessages', rid, limit);
yield put(readyPinnedMessages());
if (sub) {
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {
log('openPinnedMessagesRoom', e);
}
};
const closePinnedMessagesRoom = function* closePinnedMessagesRoom() {
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closePinnedMessagesRoom', e);
}
};
const root = function* root() {
yield takeLatest(types.PINNED_MESSAGES.OPEN, openPinnedMessagesRoom);
yield takeLatest(types.PINNED_MESSAGES.CLOSE, closePinnedMessagesRoom);
};
export default root;

View File

@ -1,43 +0,0 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyRoomFiles } from '../actions/roomFiles';
import log from '../utils/log';
let sub;
let newSub;
const openRoomFiles = function* openRoomFiles({ rid, limit }) {
try {
sub = yield RocketChat.subscribe('roomFiles', rid, limit);
yield put(readyRoomFiles());
if (sub) {
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {
log('openRoomFiles', e);
}
};
const closeRoomFiles = function* closeRoomFiles() {
try {
// yield sub.unsubscribe(sub);
// sub = null;
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeRoomFiles', e);
}
};
const root = function* root() {
yield takeLatest(types.ROOM_FILES.OPEN, openRoomFiles);
yield takeLatest(types.ROOM_FILES.CLOSE, closeRoomFiles);
};
export default root;

View File

@ -13,8 +13,6 @@ import database from '../lib/realm';
import log from '../utils/log'; import log from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
const eraseRoom = rid => RocketChat.eraseRoom(rid);
let sub; let sub;
let thread; let thread;
@ -120,25 +118,13 @@ const watchuserTyping = function* watchuserTyping({ status }) {
} }
}; };
const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) {
yield Navigation.popToRoot('RoomsListView');
try {
database.write(() => {
const messages = database.objects('messages').filtered('rid = $0', rid);
database.delete(messages);
const subscription = database.objects('subscriptions').filtered('rid = $0', rid);
database.delete(subscription);
});
} catch (error) {
console.warn('goRoomsListAndDelete', error);
}
};
const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) { const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
try { try {
sub.stop(); sub.stop();
yield RocketChat.leaveRoom(rid, t); const result = yield RocketChat.leaveRoom(rid, t);
yield goRoomsListAndDelete(rid); if (result.success) {
yield Navigation.popToRoot('RoomsListView');
}
} catch (e) { } catch (e) {
if (e.data && e.data.errorType === 'error-you-are-last-owner') { if (e.data && e.data.errorType === 'error-you-are-last-owner') {
Alert.alert(I18n.t('Oops'), I18n.t(e.data.errorType)); Alert.alert(I18n.t('Oops'), I18n.t(e.data.errorType));
@ -148,11 +134,13 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
} }
}; };
const handleEraseRoom = function* handleEraseRoom({ rid }) { const handleEraseRoom = function* handleEraseRoom({ rid, t }) {
try { try {
sub.stop(); sub.stop();
yield eraseRoom(rid); const result = yield RocketChat.eraseRoom(rid, t);
yield goRoomsListAndDelete(rid, 'erase'); if (result.success) {
yield Navigation.popToRoot('RoomsListView');
}
} catch (e) { } catch (e) {
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('erasing_room') })); Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('erasing_room') }));
} }

View File

@ -1,41 +0,0 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyStarredMessages } from '../actions/starredMessages';
import log from '../utils/log';
let sub;
let newSub;
const openStarredMessagesRoom = function* openStarredMessagesRoom({ rid, limit }) {
try {
newSub = yield RocketChat.subscribe('starredMessages', rid, limit);
yield put(readyStarredMessages());
if (sub) {
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {
log('openStarredMessagesRoom', e);
}
};
const closeStarredMessagesRoom = function* closeStarredMessagesRoom() {
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeStarredMessagesRoom', e);
}
};
const root = function* root() {
yield takeLatest(types.STARRED_MESSAGES.OPEN, openStarredMessagesRoom);
yield takeLatest(types.STARRED_MESSAGES.CLOSE, closeStarredMessagesRoom);
};
export default root;

View File

@ -3,26 +3,25 @@ import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native'; import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view'; import SafeAreaView from 'react-native-safe-area-view';
import equal from 'deep-equal';
import { openMentionedMessages as openMentionedMessagesAction, closeMentionedMessages as closeMentionedMessagesAction } from '../../actions/mentionedMessages';
import LoggedView from '../View'; import LoggedView from '../View';
import styles from './styles'; import styles from './styles';
import Message from '../../containers/message'; import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator'; import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions'; import { DEFAULT_HEADER } from '../../constants/headerOptions';
import RocketChat from '../../lib/rocketchat';
import database from '../../lib/realm';
@connect(state => ({ @connect(state => ({
messages: state.mentionedMessages.messages, baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
ready: state.mentionedMessages.ready, customEmojis: state.customEmojis,
user: { user: {
id: state.login.user && state.login.user.id, id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username, username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token token: state.login.user && state.login.user.token
} }
}), dispatch => ({
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessagesAction(rid, limit)),
closeMentionedMessages: () => dispatch(closeMentionedMessagesAction())
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class MentionedMessagesView extends LoggedView { export default class MentionedMessagesView extends LoggedView {
@ -41,53 +40,57 @@ export default class MentionedMessagesView extends LoggedView {
static propTypes = { static propTypes = {
rid: PropTypes.string, rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object, user: PropTypes.object,
openMentionedMessages: PropTypes.func, baseUrl: PropTypes.string,
closeMentionedMessages: PropTypes.func customEmojis: PropTypes.object
} }
constructor(props) { constructor(props) {
super('MentionedMessagesView', props); super('StarredMessagesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = { this.state = {
loading: true, loading: false,
loadingMore: false room: this.rooms[0],
messages: []
}; };
} }
componentDidMount() { componentDidMount() {
this.limit = 20;
this.load(); this.load();
} }
componentWillReceiveProps(nextProps) { shouldComponentUpdate(nextProps, nextState) {
const { ready } = this.props; return !equal(this.state, nextState);
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
} }
componentWillUnmount() { load = async() => {
const { closeMentionedMessages } = this.props; const {
closeMentionedMessages(); messages, total, loading, room
} } = this.state;
const { user } = this.props;
load = () => { if (messages.length === total || loading) {
const { openMentionedMessages, rid } = this.props;
openMentionedMessages(rid, this.limit);
}
moreData = () => {
const { loadingMore } = this.state;
const { messages } = this.props;
if (messages.length < this.limit) {
return; return;
} }
if (!loadingMore) {
this.setState({ loadingMore: true }); this.setState({ loading: true });
this.limit += 20;
this.load(); try {
const result = await RocketChat.getMessages(
room.rid,
room.t,
{ 'mentions._id': { $in: [user.id] } },
messages.length
);
if (result.success) {
this.setState(prevState => ({
messages: [...prevState.messages, ...result.messages],
total: result.total,
loading: false
}));
}
} catch (error) {
this.setState({ loading: false });
console.log('MentionedMessagesView -> load -> catch -> error', error);
} }
} }
@ -98,23 +101,28 @@ export default class MentionedMessagesView extends LoggedView {
) )
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { user } = this.props; const { user, customEmojis, baseUrl } = this.props;
return ( return (
<Message <Message
item={item}
style={styles.message} style={styles.message}
reactions={item.reactions} customEmojis={customEmojis}
baseUrl={baseUrl}
user={user} user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a' author={item.u}
ts={item.ts}
msg={item.msg}
attachments={item.attachments || []}
timeFormat='MMM Do YYYY, h:mm:ss a'
edited={!!item.editedAt}
header
/> />
); );
} }
render() { render() {
const { loading, loadingMore } = this.state; const { messages, loading } = this.state;
const { messages, ready } = this.props;
if (ready && messages.length === 0) { if (!loading && messages.length === 0) {
return this.renderEmpty(); return this.renderEmpty();
} }
@ -125,9 +133,8 @@ export default class MentionedMessagesView extends LoggedView {
renderItem={this.renderItem} renderItem={this.renderItem}
style={styles.list} style={styles.list}
keyExtractor={item => item._id} keyExtractor={item => item._id}
onEndReached={this.moreData} onEndReached={this.load}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null} ListFooterComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
/> />
</SafeAreaView> </SafeAreaView>
); );

View File

@ -4,32 +4,29 @@ import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ActionSheet from 'react-native-actionsheet'; import ActionSheet from 'react-native-actionsheet';
import SafeAreaView from 'react-native-safe-area-view'; import SafeAreaView from 'react-native-safe-area-view';
import equal from 'deep-equal';
import { openPinnedMessages as openPinnedMessagesAction, closePinnedMessages as closePinnedMessagesAction } from '../../actions/pinnedMessages';
import { togglePinRequest as togglePinRequestAction } from '../../actions/messages';
import LoggedView from '../View'; import LoggedView from '../View';
import styles from './styles'; import styles from './styles';
import Message from '../../containers/message'; import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator'; import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions'; import { DEFAULT_HEADER } from '../../constants/headerOptions';
import RocketChat from '../../lib/rocketchat';
import database from '../../lib/realm';
const PIN_INDEX = 0; const PIN_INDEX = 0;
const CANCEL_INDEX = 1; const CANCEL_INDEX = 1;
const options = [I18n.t('Unpin'), I18n.t('Cancel')]; const options = [I18n.t('Unpin'), I18n.t('Cancel')];
@connect(state => ({ @connect(state => ({
messages: state.pinnedMessages.messages, baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
ready: state.pinnedMessages.ready, customEmojis: state.customEmojis,
user: { user: {
id: state.login.user && state.login.user.id, id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username, username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token token: state.login.user && state.login.user.token
} }
}), dispatch => ({
openPinnedMessages: (rid, limit) => dispatch(openPinnedMessagesAction(rid, limit)),
closePinnedMessages: () => dispatch(closePinnedMessagesAction()),
togglePinRequest: message => dispatch(togglePinRequestAction(message))
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class PinnedMessagesView extends LoggedView { export default class PinnedMessagesView extends LoggedView {
@ -48,38 +45,27 @@ export default class PinnedMessagesView extends LoggedView {
static propTypes = { static propTypes = {
rid: PropTypes.string, rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object, user: PropTypes.object,
openPinnedMessages: PropTypes.func, baseUrl: PropTypes.string,
closePinnedMessages: PropTypes.func, customEmojis: PropTypes.object
togglePinRequest: PropTypes.func
} }
constructor(props) { constructor(props) {
super('PinnedMessagesView', props); super('PinnedMessagesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = { this.state = {
message: {}, loading: false,
loading: true, room: this.rooms[0],
loadingMore: false messages: []
}; };
} }
componentDidMount() { componentDidMount() {
this.limit = 20;
this.load(); this.load();
} }
componentWillReceiveProps(nextProps) { shouldComponentUpdate(nextProps, nextState) {
const { ready } = this.props; return !equal(this.state, nextState);
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
}
componentWillUnmount() {
const { closePinnedMessages } = this.props;
closePinnedMessages();
} }
onLongPress = (message) => { onLongPress = (message) => {
@ -90,33 +76,51 @@ export default class PinnedMessagesView extends LoggedView {
} }
handleActionPress = (actionIndex) => { handleActionPress = (actionIndex) => {
const { message } = this.state;
const { togglePinRequest } = this.props;
switch (actionIndex) { switch (actionIndex) {
case PIN_INDEX: case PIN_INDEX:
togglePinRequest(message); this.unPin();
break; break;
default: default:
break; break;
} }
} }
load = () => { unPin = async() => {
const { openPinnedMessages, rid } = this.props; const { message } = this.state;
openPinnedMessages(rid, this.limit); try {
const result = await RocketChat.togglePinMessage(message);
if (result.success) {
this.setState(prevState => ({
messages: prevState.messages.filter(item => item._id !== message._id)
}));
}
} catch (error) {
console.log('PinnedMessagesView -> unPin -> catch -> error', error);
}
} }
moreData = () => { load = async() => {
const { loadingMore } = this.state; const {
const { messages } = this.props; messages, total, loading, room
if (messages.length < this.limit) { } = this.state;
if (messages.length === total || loading) {
return; return;
} }
if (!loadingMore) {
this.setState({ loadingMore: true }); this.setState({ loading: true });
this.limit += 20;
this.load(); try {
const result = await RocketChat.getMessages(room.rid, room.t, { pinned: true }, messages.length);
if (result.success) {
this.setState(prevState => ({
messages: [...prevState.messages, ...result.messages],
total: result.total,
loading: false
}));
}
} catch (error) {
this.setState({ loading: false });
console.log('PinnedMessagesView -> catch -> error', error);
} }
} }
@ -127,24 +131,29 @@ export default class PinnedMessagesView extends LoggedView {
) )
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { user } = this.props; const { user, customEmojis, baseUrl } = this.props;
return ( return (
<Message <Message
item={item}
style={styles.message} style={styles.message}
reactions={item.reactions} customEmojis={customEmojis}
baseUrl={baseUrl}
user={user} user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a' author={item.u}
onLongPress={this.onLongPress} ts={item.ts}
msg={item.msg}
attachments={item.attachments || []}
timeFormat='MMM Do YYYY, h:mm:ss a'
header
edited={!!item.editedAt}
onLongPress={() => this.onLongPress(item)}
/> />
); );
} }
render() { render() {
const { loading, loadingMore } = this.state; const { messages, loading } = this.state;
const { messages, ready } = this.props;
if (ready && messages.length === 0) { if (!loading && messages.length === 0) {
return this.renderEmpty(); return this.renderEmpty();
} }
@ -155,9 +164,8 @@ export default class PinnedMessagesView extends LoggedView {
renderItem={this.renderItem} renderItem={this.renderItem}
style={styles.list} style={styles.list}
keyExtractor={item => item._id} keyExtractor={item => item._id}
onEndReached={this.moreData} onEndReached={this.load}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null} ListFooterComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
/> />
<ActionSheet <ActionSheet
ref={o => this.actionSheet = o} ref={o => this.actionSheet = o}

View File

@ -20,7 +20,6 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { showErrorAlert, showToast } from '../../utils/info'; import { showErrorAlert, showToast } from '../../utils/info';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import RCTextInput from '../../containers/TextInput'; import RCTextInput from '../../containers/TextInput';
import Loading from '../../containers/Loading';
import log from '../../utils/log'; import log from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
@ -29,9 +28,11 @@ import Touch from '../../utils/touch';
import Drawer from '../../Drawer'; import Drawer from '../../Drawer';
import { DEFAULT_HEADER } from '../../constants/headerOptions'; import { DEFAULT_HEADER } from '../../constants/headerOptions';
import { appStart as appStartAction } from '../../actions'; import { appStart as appStartAction } from '../../actions';
import { setUser as setUserAction } from '../../actions/login';
@connect(state => ({ @connect(state => ({
user: { user: {
id: state.login.user && state.login.user.id,
name: state.login.user && state.login.user.name, name: state.login.user && state.login.user.name,
username: state.login.user && state.login.user.username, username: state.login.user && state.login.user.username,
customFields: state.login.user && state.login.user.customFields, customFields: state.login.user && state.login.user.customFields,
@ -40,7 +41,8 @@ import { appStart as appStartAction } from '../../actions';
Accounts_CustomFields: state.settings.Accounts_CustomFields, Accounts_CustomFields: state.settings.Accounts_CustomFields,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}), dispatch => ({ }), dispatch => ({
appStart: () => dispatch(appStartAction()) appStart: () => dispatch(appStartAction()),
setUser: params => dispatch(setUserAction(params))
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class ProfileView extends LoggedView { export default class ProfileView extends LoggedView {
@ -75,7 +77,8 @@ export default class ProfileView extends LoggedView {
componentId: PropTypes.string, componentId: PropTypes.string,
user: PropTypes.object, user: PropTypes.object,
Accounts_CustomFields: PropTypes.string, Accounts_CustomFields: PropTypes.string,
appStart: PropTypes.func appStart: PropTypes.func,
setUser: PropTypes.func
} }
constructor(props) { constructor(props) {
@ -87,7 +90,7 @@ export default class ProfileView extends LoggedView {
username: null, username: null,
email: null, email: null,
newPassword: null, newPassword: null,
typedPassword: null, currentPassword: null,
avatarUrl: null, avatarUrl: null,
avatar: {}, avatar: {},
avatarSuggestions: {}, avatarSuggestions: {},
@ -146,7 +149,7 @@ export default class ProfileView extends LoggedView {
username, username,
email: emails ? emails[0].address : null, email: emails ? emails[0].address : null,
newPassword: null, newPassword: null,
typedPassword: null, currentPassword: null,
avatarUrl: null, avatarUrl: null,
avatar: {}, avatar: {},
customFields: customFields || {} customFields: customFields || {}
@ -163,7 +166,7 @@ export default class ProfileView extends LoggedView {
const customFieldsKeys = Object.keys(customFields); const customFieldsKeys = Object.keys(customFields);
if (customFieldsKeys.length) { if (customFieldsKeys.length) {
customFieldsKeys.forEach((key) => { customFieldsKeys.forEach((key) => {
if (user.customFields[key] !== customFields[key]) { if (!user.customFields || user.customFields[key] !== customFields[key]) {
customFieldsChanged = true; customFieldsChanged = true;
} }
}); });
@ -183,13 +186,8 @@ export default class ProfileView extends LoggedView {
} }
handleError = (e, func, action) => { handleError = (e, func, action) => {
if (e && e.error && e.error !== 500) { if (e.data && e.data.errorType === 'error-too-many-requests') {
if (e.details && e.details.timeToReset) { return showErrorAlert(e.data.error);
return showErrorAlert(I18n.t('error-too-many-requests', {
seconds: parseInt(e.details.timeToReset / 1000, 10)
}));
}
return showErrorAlert(I18n.t(e.error, e.details));
} }
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t(action) })); showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t(action) }));
log(func, e); log(func, e);
@ -205,14 +203,14 @@ export default class ProfileView extends LoggedView {
this.setState({ saving: true, showPasswordAlert: false }); this.setState({ saving: true, showPasswordAlert: false });
const { const {
name, username, email, newPassword, typedPassword, avatar, customFields name, username, email, newPassword, currentPassword, avatar, customFields
} = this.state; } = this.state;
const { user } = this.props; const { user, setUser } = this.props;
const params = {}; const params = {};
// Name // Name
if (user.name !== name) { if (user.name !== name) {
params.realname = name; params.name = name;
} }
// Username // Username
@ -230,13 +228,13 @@ export default class ProfileView extends LoggedView {
params.newPassword = newPassword; params.newPassword = newPassword;
} }
// typedPassword // currentPassword
if (typedPassword) { if (currentPassword) {
params.typedPassword = SHA256(typedPassword); params.currentPassword = SHA256(currentPassword);
} }
const requirePassword = !!params.email || newPassword; const requirePassword = !!params.email || newPassword;
if (requirePassword && !params.typedPassword) { if (requirePassword && !params.currentPassword) {
return this.setState({ showPasswordAlert: true, saving: false }); return this.setState({ showPasswordAlert: true, saving: false });
} }
@ -245,28 +243,32 @@ export default class ProfileView extends LoggedView {
try { try {
await RocketChat.setAvatarFromService(avatar); await RocketChat.setAvatarFromService(avatar);
} catch (e) { } catch (e) {
this.setState({ saving: false, typedPassword: null }); this.setState({ saving: false, currentPassword: null });
return setTimeout(() => this.handleError(e, 'setAvatarFromService', 'changing_avatar'), 300); return this.handleError(e, 'setAvatarFromService', 'changing_avatar');
} }
} }
await RocketChat.saveUserProfile(params, customFields); params.customFields = customFields;
this.setState({ saving: false });
setTimeout(() => { const result = await RocketChat.saveUserProfile(params);
if (result.success) {
if (params.customFields) {
setUser({ customFields });
}
this.setState({ saving: false });
showToast(I18n.t('Profile_saved_successfully')); showToast(I18n.t('Profile_saved_successfully'));
this.init(); this.init();
}, 300); }
} catch (e) { } catch (e) {
this.setState({ saving: false, typedPassword: null }); this.setState({ saving: false, currentPassword: null });
setTimeout(() => { this.handleError(e, 'saveUserProfile', 'saving_profile');
this.handleError(e, 'saveUserProfile', 'saving_profile');
}, 300);
} }
} }
resetAvatar = async() => { resetAvatar = async() => {
try { try {
await RocketChat.resetAvatar(); const { user } = this.props;
await RocketChat.resetAvatar(user.id);
showToast(I18n.t('Avatar_changed_successfully')); showToast(I18n.t('Avatar_changed_successfully'));
this.init(); this.init();
} catch (e) { } catch (e) {
@ -353,53 +355,57 @@ export default class ProfileView extends LoggedView {
if (!Accounts_CustomFields) { if (!Accounts_CustomFields) {
return null; return null;
} }
const parsedCustomFields = JSON.parse(Accounts_CustomFields); try {
return Object.keys(parsedCustomFields).map((key, index, array) => { const parsedCustomFields = JSON.parse(Accounts_CustomFields);
if (parsedCustomFields[key].type === 'select') { return Object.keys(parsedCustomFields).map((key, index, array) => {
const options = parsedCustomFields[key].options.map(option => ({ label: option, value: option })); if (parsedCustomFields[key].type === 'select') {
const options = parsedCustomFields[key].options.map(option => ({ label: option, value: option }));
return (
<RNPickerSelect
key={key}
items={options}
onValueChange={(value) => {
const newValue = {};
newValue[key] = value;
this.setState({ customFields: { ...customFields, ...newValue } });
}}
value={customFields[key]}
>
<RCTextInput
inputRef={(e) => { this[key] = e; }}
label={key}
placeholder={key}
value={customFields[key]}
testID='settings-view-language'
/>
</RNPickerSelect>
);
}
return ( return (
<RNPickerSelect <RCTextInput
inputRef={(e) => { this[key] = e; }}
key={key} key={key}
items={options} label={key}
onValueChange={(value) => { placeholder={key}
value={customFields[key]}
onChangeText={(value) => {
const newValue = {}; const newValue = {};
newValue[key] = value; newValue[key] = value;
this.setState({ customFields: { ...customFields, ...newValue } }); this.setState({ customFields: { ...customFields, ...newValue } });
}} }}
value={customFields[key]} onSubmitEditing={() => {
> if (array.length - 1 > index) {
<RCTextInput return this[array[index + 1]].focus();
inputRef={(e) => { this[key] = e; }} }
label={key} this.avatarUrl.focus();
placeholder={key} }}
value={customFields[key]} />
testID='settings-view-language'
/>
</RNPickerSelect>
); );
} });
} catch (error) {
return ( return null;
<RCTextInput }
inputRef={(e) => { this[key] = e; }}
key={key}
label={key}
placeholder={key}
value={customFields[key]}
onChangeText={(value) => {
const newValue = {};
newValue[key] = value;
this.setState({ customFields: { ...customFields, ...newValue } });
}}
onSubmitEditing={() => {
if (array.length - 1 > index) {
return this[array[index + 1]].focus();
}
this.avatarUrl.focus();
}}
/>
);
});
} }
render() { render() {
@ -480,16 +486,14 @@ export default class ProfileView extends LoggedView {
testID='profile-view-avatar-url' testID='profile-view-avatar-url'
/> />
{this.renderAvatarButtons()} {this.renderAvatarButtons()}
<View style={sharedStyles.alignItemsFlexStart}> <Button
<Button title={I18n.t('Save_Changes')}
title={I18n.t('Save_Changes')} type='primary'
type='primary' onPress={this.submit}
onPress={this.submit} disabled={!this.formIsChanged()}
disabled={!this.formIsChanged()} testID='profile-view-submit'
testID='profile-view-submit' loading={saving}
/> />
</View>
<Loading visible={saving} />
<Dialog.Container visible={showPasswordAlert}> <Dialog.Container visible={showPasswordAlert}>
<Dialog.Title> <Dialog.Title>
{I18n.t('Please_enter_your_password')} {I18n.t('Please_enter_your_password')}
@ -498,7 +502,7 @@ export default class ProfileView extends LoggedView {
{I18n.t('For_your_security_you_must_enter_your_current_password_to_continue')} {I18n.t('For_your_security_you_must_enter_your_current_password_to_continue')}
</Dialog.Description> </Dialog.Description>
<Dialog.Input <Dialog.Input
onChangeText={value => this.setState({ typedPassword: value })} onChangeText={value => this.setState({ currentPassword: value })}
secureTextEntry secureTextEntry
testID='profile-view-typed-password' testID='profile-view-typed-password'
style={styles.dialogInput} style={styles.dialogInput}

View File

@ -68,7 +68,9 @@ export default class RoomActionsView extends LoggedView {
this.state = { this.state = {
room: this.rooms[0] || {}, room: this.rooms[0] || {},
membersCount: 0, membersCount: 0,
member: {} member: {},
joined: false,
canViewMembers: false
}; };
} }
@ -86,6 +88,9 @@ export default class RoomActionsView extends LoggedView {
} }
} }
if (room.t === 'd') {
this.updateRoomMember();
}
this.rooms.addListener(this.updateRoom); this.rooms.addListener(this.updateRoom);
} }
@ -310,6 +315,20 @@ export default class RoomActionsView extends LoggedView {
this.setState({ room: this.rooms[0] || {} }); this.setState({ room: this.rooms[0] || {} });
} }
updateRoomMember = async() => {
const { room } = this.state;
const { rid } = room;
const { userId } = this.props;
try {
const member = await RocketChat.getRoomMember(rid, userId);
this.setState({ member });
} catch (e) {
log('RoomActions updateRoomMember', e);
return {};
}
}
toggleBlockUser = () => { toggleBlockUser = () => {
const { room } = this.state; const { room } = this.state;
const { rid, blocker } = room; const { rid, blocker } = room;

View File

@ -3,26 +3,25 @@ import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native'; import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view'; import SafeAreaView from 'react-native-safe-area-view';
import equal from 'deep-equal';
import { openRoomFiles as openRoomFilesAction, closeRoomFiles as closeRoomFilesAction } from '../../actions/roomFiles';
import LoggedView from '../View'; import LoggedView from '../View';
import styles from './styles'; import styles from './styles';
import Message from '../../containers/message'; import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator'; import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions'; import { DEFAULT_HEADER } from '../../constants/headerOptions';
import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
@connect(state => ({ @connect(state => ({
messages: state.roomFiles.messages, baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
ready: state.roomFiles.ready, customEmojis: state.customEmojis,
user: { user: {
id: state.login.user && state.login.user.id, id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username, username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token token: state.login.user && state.login.user.token
} }
}), dispatch => ({
openRoomFiles: (rid, limit) => dispatch(openRoomFilesAction(rid, limit)),
closeRoomFiles: () => dispatch(closeRoomFilesAction())
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class RoomFilesView extends LoggedView { export default class RoomFilesView extends LoggedView {
@ -41,53 +40,51 @@ export default class RoomFilesView extends LoggedView {
static propTypes = { static propTypes = {
rid: PropTypes.string, rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object, user: PropTypes.object,
openRoomFiles: PropTypes.func, baseUrl: PropTypes.string,
closeRoomFiles: PropTypes.func customEmojis: PropTypes.object
} }
constructor(props) { constructor(props) {
super('RoomFilesView', props); super('RoomFilesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = { this.state = {
loading: true, loading: false,
loadingMore: false room: this.rooms[0],
messages: []
}; };
} }
componentDidMount() { componentDidMount() {
this.limit = 20;
this.load(); this.load();
} }
componentWillReceiveProps(nextProps) { shouldComponentUpdate(nextProps, nextState) {
const { ready } = this.props; return !equal(this.state, nextState);
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
} }
componentWillUnmount() { load = async() => {
const { closeRoomFiles } = this.props; const {
closeRoomFiles(); messages, total, loading, room
} } = this.state;
if (messages.length === total || loading) {
load = () => {
const { openRoomFiles, rid } = this.props;
openRoomFiles(rid, this.limit);
}
moreData = () => {
const { loadingMore } = this.state;
const { messages } = this.props;
if (messages.length < this.limit) {
return; return;
} }
if (!loadingMore) {
this.setState({ loadingMore: true }); this.setState({ loading: true });
this.limit += 20;
this.load(); try {
const result = await RocketChat.getFiles(room.rid, room.t, messages.length);
if (result.success) {
this.setState(prevState => ({
messages: [...prevState.messages, ...result.files],
total: result.total,
loading: false
}));
}
} catch (error) {
this.setState({ loading: false });
console.log('RoomFilesView -> catch -> error', error);
} }
} }
@ -98,26 +95,49 @@ export default class RoomFilesView extends LoggedView {
) )
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { user } = this.props; const { user, baseUrl, customEmojis } = this.props;
let url = {};
if (/image/.test(item.type)) {
url = { image_url: item.url };
} else if (/audio/.test(item.type)) {
url = { audio_url: item.url };
} else if (/video/.test(item.type)) {
url = { video_url: item.url };
} else {
url = {
title_link: item.url,
type: 'file'
};
}
return ( return (
<Message <Message
item={item}
style={styles.message} style={styles.message}
reactions={item.reactions} customEmojis={customEmojis}
baseUrl={baseUrl}
user={user} user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a' author={item.user}
ts={item.uploadedAt}
attachments={[{
title: item.name,
description: item.description,
...url
}]}
timeFormat='MMM Do YYYY, h:mm:ss a'
edited={!!item.editedAt}
header
/> />
); );
} }
render() { render() {
const { messages, ready } = this.props; const { messages, loading } = this.state;
if (ready && messages.length === 0) {
if (!loading && messages.length === 0) {
return this.renderEmpty(); return this.renderEmpty();
} }
const { loading, loadingMore } = this.state;
return ( return (
<SafeAreaView style={styles.list} testID='room-files-view' forceInset={{ bottom: 'never' }}> <SafeAreaView style={styles.list} testID='room-files-view' forceInset={{ bottom: 'never' }}>
<FlatList <FlatList
@ -125,9 +145,8 @@ export default class RoomFilesView extends LoggedView {
renderItem={this.renderItem} renderItem={this.renderItem}
style={styles.list} style={styles.list}
keyExtractor={item => item._id} keyExtractor={item => item._id}
onEndReached={this.moreData} onEndReached={this.load}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null} ListFooterComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
/> />
</SafeAreaView> </SafeAreaView>
); );

View File

@ -39,7 +39,7 @@ const PERMISSIONS_ARRAY = [
]; ];
@connect(null, dispatch => ({ @connect(null, dispatch => ({
eraseRoom: rid => dispatch(eraseRoomAction(rid)) eraseRoom: (rid, t) => dispatch(eraseRoomAction(rid, t))
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class RoomInfoEditView extends LoggedView { export default class RoomInfoEditView extends LoggedView {
@ -231,7 +231,7 @@ export default class RoomInfoEditView extends LoggedView {
{ {
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
style: 'destructive', style: 'destructive',
onPress: () => eraseRoom(room.rid) onPress: () => eraseRoom(room.rid, room.t)
} }
], ],
{ cancelable: false } { cancelable: false }
@ -240,7 +240,7 @@ export default class RoomInfoEditView extends LoggedView {
toggleArchive = () => { toggleArchive = () => {
const { room } = this.state; const { room } = this.state;
const { rid, archived } = room; const { rid, archived, t } = room;
const action = I18n.t(`${ archived ? 'un' : '' }archive`); const action = I18n.t(`${ archived ? 'un' : '' }archive`);
Alert.alert( Alert.alert(
@ -254,9 +254,9 @@ export default class RoomInfoEditView extends LoggedView {
{ {
text: I18n.t('Yes_action_it', { action }), text: I18n.t('Yes_action_it', { action }),
style: 'destructive', style: 'destructive',
onPress: () => { onPress: async() => {
try { try {
RocketChat.toggleArchiveRoom(rid, !archived); await RocketChat.toggleArchiveRoom(rid, t, !archived);
} catch (e) { } catch (e) {
log('toggleArchive', e); log('toggleArchive', e);
} }

View File

@ -81,8 +81,8 @@ export default class RoomMembersView extends LoggedView {
this.rooms.removeAllListeners(); this.rooms.removeAllListeners();
} }
navigationButtonPressed = async({ buttonId }) => { navigationButtonPressed = ({ buttonId }) => {
const { rid, allUsers } = this.state; const { allUsers } = this.state;
const { componentId } = this.props; const { componentId } = this.props;
if (buttonId === 'toggleOnline') { if (buttonId === 'toggleOnline') {
@ -97,10 +97,7 @@ export default class RoomMembersView extends LoggedView {
}] }]
} }
}); });
const allUsersFilter = !allUsers; this.fetchMembers(!allUsers);
const membersResult = await RocketChat.getRoomMembers(rid, allUsersFilter);
const members = membersResult.records;
this.setState({ allUsers: allUsersFilter, members });
} catch (e) { } catch (e) {
log('RoomMembers.onNavigationButtonPressed', e); log('RoomMembers.onNavigationButtonPressed', e);
} }

View File

@ -133,6 +133,12 @@ export default class RoomView extends LoggedView {
return true; return true;
} else if (room.f !== nextState.room.f) { } else if (room.f !== nextState.room.f) {
return true; return true;
} else if (room.blocked !== nextState.room.blocked) {
return true;
} else if (room.blocker !== nextState.room.blocker) {
return true;
} else if (room.archived !== nextState.room.archived) {
return true;
} else if (loaded !== nextState.loaded) { } else if (loaded !== nextState.loaded) {
return true; return true;
} else if (joined !== nextState.joined) { } else if (joined !== nextState.joined) {
@ -156,17 +162,21 @@ export default class RoomView extends LoggedView {
const { componentId, appState } = this.props; const { componentId, appState } = this.props;
if (prevState.room.f !== room.f) { if (prevState.room.f !== room.f) {
const rightButtons = [{
id: 'star',
testID: 'room-view-header-star',
icon: room.f ? iconsMap.star : iconsMap.starOutline
}];
if (room.t !== 'l') {
rightButtons.unshift({
id: 'more',
testID: 'room-view-header-actions',
icon: iconsMap.more
});
}
Navigation.mergeOptions(componentId, { Navigation.mergeOptions(componentId, {
topBar: { topBar: {
rightButtons: [{ rightButtons
id: 'more',
testID: 'room-view-header-actions',
icon: iconsMap.more
}, {
id: 'star',
testID: 'room-view-header-star',
icon: room.f ? iconsMap.star : iconsMap.starOutline
}]
} }
}); });
} else if (appState === 'foreground' && appState !== prevProps.appState) { } else if (appState === 'foreground' && appState !== prevProps.appState) {
@ -375,14 +385,14 @@ export default class RoomView extends LoggedView {
} }
if (room.archived || this.isReadOnly()) { if (room.archived || this.isReadOnly()) {
return ( return (
<View style={styles.readOnly}> <View style={styles.readOnly} key='room-view-read-only'>
<Text>{I18n.t('This_room_is_read_only')}</Text> <Text>{I18n.t('This_room_is_read_only')}</Text>
</View> </View>
); );
} }
if (this.isBlocked()) { if (this.isBlocked()) {
return ( return (
<View style={styles.blockedOrBlocker}> <View style={styles.readOnly} key='room-view-block'>
<Text>{I18n.t('This_room_is_blocked')}</Text> <Text>{I18n.t('This_room_is_blocked')}</Text>
</View> </View>
); );

View File

@ -32,10 +32,9 @@ export default StyleSheet.create({
color: '#ccc' color: '#ccc'
}, },
readOnly: { readOnly: {
padding: 10 justifyContent: 'flex-end',
}, alignItems: 'center',
blockedOrBlocker: { marginVertical: 15
padding: 10
}, },
reactionPickerContainer: { reactionPickerContainer: {
// width: width - 20, // width: width - 20,

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, FlatList } from 'react-native'; import { View, FlatList, Text } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view'; import SafeAreaView from 'react-native-safe-area-view';
@ -11,20 +11,20 @@ import styles from './styles';
import Markdown from '../../containers/message/Markdown'; import Markdown from '../../containers/message/Markdown';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import buildMessage from '../../lib/methods/helpers/buildMessage'; import Message from '../../containers/message/Message';
import Message from '../../containers/message';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
import log from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions'; import { DEFAULT_HEADER } from '../../constants/headerOptions';
import database from '../../lib/realm';
@connect(state => ({ @connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
customEmojis: state.customEmojis,
user: { user: {
id: state.login.user && state.login.user.id, id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username, username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token token: state.login.user && state.login.user.token
}, }
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class SearchMessagesView extends LoggedView { export default class SearchMessagesView extends LoggedView {
@ -43,18 +43,19 @@ export default class SearchMessagesView extends LoggedView {
static propTypes = { static propTypes = {
rid: PropTypes.string, rid: PropTypes.string,
componentId: PropTypes.string,
user: PropTypes.object, user: PropTypes.object,
baseUrl: PropTypes.string baseUrl: PropTypes.string,
customEmojis: PropTypes.object
} }
constructor(props) { constructor(props) {
super('SearchMessagesView', props); super('SearchMessagesView', props);
this.limit = 0; this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = { this.state = {
loading: false,
room: this.rooms[0],
messages: [], messages: [],
searching: false, searchText: ''
loadingMore: false
}; };
} }
@ -63,100 +64,88 @@ export default class SearchMessagesView extends LoggedView {
} }
componentWillUnmount() { componentWillUnmount() {
this.onChangeSearch.stop(); this.search.stop();
} }
onChangeSearch = debounce((search) => { // eslint-disable-next-line react/sort-comp
const { searching } = this.state; search = debounce(async(searchText) => {
const { room } = this.state;
this.setState({ searchText, loading: true, messages: [] });
this.searchText = search; try {
this.limit = 0; const result = await RocketChat.searchMessages(room.rid, searchText);
if (!searching) { if (result.success) {
this.setState({ searching: true }); this.setState({
messages: result.messages || [],
loading: false
});
}
} catch (error) {
this.setState({ loading: false });
console.log('SearchMessagesView -> search -> catch -> error', error);
} }
this.search();
}, 1000) }, 1000)
search = async() => { renderEmpty = () => (
const { rid } = this.props; <View style={styles.listEmptyContainer}>
<Text>{I18n.t('No_results_found')}</Text>
if (this._cancel) { </View>
this._cancel('cancel'); )
}
const cancel = new Promise((r, reject) => this._cancel = reject);
let messages = [];
try {
const result = await Promise.race([RocketChat.messageSearch(this.searchText, rid, this.limit), cancel]);
messages = result.message.docs.map(message => buildMessage(message));
this.setState({ messages, searching: false, loadingMore: false });
} catch (e) {
this._cancel = null;
if (e !== 'cancel') {
return this.setState({ searching: false, loadingMore: false });
}
log('SearchMessagesView.search', e);
}
}
moreData = () => {
const { loadingMore, messages } = this.state;
if (messages.length < this.limit) {
return;
}
if (this.searchText && !loadingMore) {
this.setState({ loadingMore: true });
this.limit += 20;
this.search();
}
}
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { user } = this.props; const { user, customEmojis, baseUrl } = this.props;
return ( return (
<Message <Message
item={item}
style={styles.message} style={styles.message}
reactions={item.reactions} customEmojis={customEmojis}
baseUrl={baseUrl}
user={user} user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a' author={item.u}
onReactionPress={async(emoji) => { ts={item.ts}
try { msg={item.msg}
await RocketChat.setReaction(emoji, item._id); attachments={item.attachments || []}
this.search(); timeFormat='MMM Do YYYY, h:mm:ss a'
this.forceUpdate(); edited={!!item.editedAt}
} catch (e) { header
log('SearchMessagesView.onReactionPress', e); />
} );
}} }
renderList = () => {
const { messages, loading, searchText } = this.state;
if (!loading && messages.length === 0 && searchText.length) {
return this.renderEmpty();
}
return (
<FlatList
data={messages}
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
onEndReached={this.load}
ListFooterComponent={loading ? <RCActivityIndicator /> : null}
{...scrollPersistTaps}
/> />
); );
} }
render() { render() {
const { searching, loadingMore, messages } = this.state;
return ( return (
<SafeAreaView style={styles.container} testID='search-messages-view' forceInset={{ bottom: 'never' }}> <SafeAreaView style={styles.container} testID='search-messages-view' forceInset={{ bottom: 'never' }}>
<View style={styles.searchContainer}> <View style={styles.searchContainer}>
<RCTextInput <RCTextInput
inputRef={(e) => { this.name = e; }} inputRef={(e) => { this.name = e; }}
label={I18n.t('Search')} label={I18n.t('Search')}
onChangeText={this.onChangeSearch} onChangeText={this.search}
placeholder={I18n.t('Search_Messages')} placeholder={I18n.t('Search_Messages')}
testID='search-message-view-input' testID='search-message-view-input'
/> />
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} username='' baseUrl='' customEmojis={{}} /> <Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} username='' baseUrl='' customEmojis={{}} />
<View style={styles.divider} /> <View style={styles.divider} />
</View> </View>
<FlatList {this.renderList()}
data={messages}
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
onEndReached={this.moreData}
ListHeaderComponent={searching ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
{...scrollPersistTaps}
/>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -17,9 +17,9 @@ export default StyleSheet.create({
transform: [{ scaleY: 1 }] transform: [{ scaleY: 1 }]
}, },
divider: { divider: {
width: '100%',
height: StyleSheet.hairlineWidth, height: StyleSheet.hairlineWidth,
borderColor: '#ddd', backgroundColor: '#E7EBF2',
borderBottomWidth: StyleSheet.hairlineWidth,
marginVertical: 20 marginVertical: 20
} }
}); });

View File

@ -105,7 +105,7 @@ export default class SnippetedMessagesView extends LoggedView {
style={styles.message} style={styles.message}
reactions={item.reactions} reactions={item.reactions}
user={user} user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a' customTimeFormat='MMM Do YYYY, h:mm:ss a'
/> />
); );
} }

View File

@ -4,32 +4,29 @@ import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ActionSheet from 'react-native-actionsheet'; import ActionSheet from 'react-native-actionsheet';
import SafeAreaView from 'react-native-safe-area-view'; import SafeAreaView from 'react-native-safe-area-view';
import equal from 'deep-equal';
import { openStarredMessages as openStarredMessagesAction, closeStarredMessages as closeStarredMessagesAction } from '../../actions/starredMessages';
import { toggleStarRequest as toggleStarRequestAction } from '../../actions/messages';
import LoggedView from '../View'; import LoggedView from '../View';
import styles from './styles'; import styles from './styles';
import Message from '../../containers/message'; import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator'; import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions'; import { DEFAULT_HEADER } from '../../constants/headerOptions';
import RocketChat from '../../lib/rocketchat';
import database from '../../lib/realm';
const STAR_INDEX = 0; const STAR_INDEX = 0;
const CANCEL_INDEX = 1; const CANCEL_INDEX = 1;
const options = [I18n.t('Unstar'), I18n.t('Cancel')]; const options = [I18n.t('Unstar'), I18n.t('Cancel')];
@connect(state => ({ @connect(state => ({
messages: state.starredMessages.messages, baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
ready: state.starredMessages.ready, customEmojis: state.customEmojis,
user: { user: {
id: state.login.user && state.login.user.id, id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username, username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token token: state.login.user && state.login.user.token
} }
}), dispatch => ({
openStarredMessages: (rid, limit) => dispatch(openStarredMessagesAction(rid, limit)),
closeStarredMessages: () => dispatch(closeStarredMessagesAction()),
toggleStarRequest: message => dispatch(toggleStarRequestAction(message))
})) }))
/** @extends React.Component */ /** @extends React.Component */
export default class StarredMessagesView extends LoggedView { export default class StarredMessagesView extends LoggedView {
@ -48,38 +45,27 @@ export default class StarredMessagesView extends LoggedView {
static propTypes = { static propTypes = {
rid: PropTypes.string, rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object, user: PropTypes.object,
openStarredMessages: PropTypes.func, baseUrl: PropTypes.string,
closeStarredMessages: PropTypes.func, customEmojis: PropTypes.object
toggleStarRequest: PropTypes.func
} }
constructor(props) { constructor(props) {
super('StarredMessagesView', props); super('StarredMessagesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = { this.state = {
message: {}, loading: false,
loading: true, room: this.rooms[0],
loadingMore: false messages: []
}; };
} }
componentDidMount() { componentDidMount() {
this.limit = 20;
this.load(); this.load();
} }
componentWillReceiveProps(nextProps) { shouldComponentUpdate(nextProps, nextState) {
const { ready } = this.props; return !equal(this.state, nextState);
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
}
componentWillUnmount() {
const { closeStarredMessages } = this.props;
closeStarredMessages();
} }
onLongPress = (message) => { onLongPress = (message) => {
@ -90,33 +76,57 @@ export default class StarredMessagesView extends LoggedView {
} }
handleActionPress = (actionIndex) => { handleActionPress = (actionIndex) => {
const { message } = this.state;
const { toggleStarRequest } = this.props;
switch (actionIndex) { switch (actionIndex) {
case STAR_INDEX: case STAR_INDEX:
toggleStarRequest(message); this.unStar();
break; break;
default: default:
break; break;
} }
} }
load = () => { unStar = async() => {
const { rid, openStarredMessages } = this.props; const { message } = this.state;
openStarredMessages(rid, this.limit); try {
const result = await RocketChat.toggleStarMessage(message);
if (result.success) {
this.setState(prevState => ({
messages: prevState.messages.filter(item => item._id !== message._id)
}));
}
} catch (error) {
console.log('StarredMessagesView -> unStar -> catch -> error', error);
}
} }
moreData = () => { load = async() => {
const { loadingMore } = this.state; const {
const { messages } = this.props; messages, total, loading, room
if (messages.length < this.limit) { } = this.state;
const { user } = this.props;
if (messages.length === total || loading) {
return; return;
} }
if (!loadingMore) {
this.setState({ loadingMore: true }); this.setState({ loading: true });
this.limit += 20;
this.load(); try {
const result = await RocketChat.getMessages(
room.rid,
room.t,
{ 'starred._id': { $in: [user.id] } },
messages.length
);
if (result.success) {
this.setState(prevState => ({
messages: [...prevState.messages, ...result.messages],
total: result.total,
loading: false
}));
}
} catch (error) {
this.setState({ loading: false });
console.log('StarredMessagesView -> load -> catch -> error', error);
} }
} }
@ -127,24 +137,29 @@ export default class StarredMessagesView extends LoggedView {
) )
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { user } = this.props; const { user, customEmojis, baseUrl } = this.props;
return ( return (
<Message <Message
item={item}
style={styles.message} style={styles.message}
reactions={item.reactions} customEmojis={customEmojis}
baseUrl={baseUrl}
user={user} user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a' author={item.u}
onLongPress={this.onLongPress} ts={item.ts}
msg={item.msg}
attachments={item.attachments || []}
timeFormat='MMM Do YYYY, h:mm:ss a'
edited={!!item.editedAt}
header
onLongPress={() => this.onLongPress(item)}
/> />
); );
} }
render() { render() {
const { loading, loadingMore } = this.state; const { messages, loading } = this.state;
const { messages, ready } = this.props;
if (ready && messages.length === 0) { if (!loading && messages.length === 0) {
return this.renderEmpty(); return this.renderEmpty();
} }
@ -155,9 +170,8 @@ export default class StarredMessagesView extends LoggedView {
renderItem={this.renderItem} renderItem={this.renderItem}
style={styles.list} style={styles.list}
keyExtractor={item => item._id} keyExtractor={item => item._id}
onEndReached={this.moreData} onEndReached={this.load}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null} ListFooterComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
/> />
<ActionSheet <ActionSheet
ref={o => this.actionSheet = o} ref={o => this.actionSheet = o}