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 ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['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 ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
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_announcement',
'room_changed_topic',
'room_changed_privacy'
'room_changed_privacy',
'message_snippeted'
];
const getInfoMessage = ({
@ -76,6 +77,8 @@ const getInfoMessage = ({
return I18n.t('Room_changed_topic', { topic: msg, userBy: username });
} else if (type === 'room_changed_privacy') {
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
} else if (type === 'message_snippeted') {
return I18n.t('Created_snippet');
}
return '';
};
@ -107,7 +110,10 @@ export default class Message extends PureComponent {
header: PropTypes.bool,
avatar: PropTypes.string,
alias: PropTypes.string,
ts: PropTypes.instanceOf(Date),
ts: PropTypes.oneOfType([
PropTypes.instanceOf(Date),
PropTypes.string
]),
edited: PropTypes.bool,
attachments: PropTypes.oneOfType([
PropTypes.array,

View File

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

View File

@ -131,6 +131,7 @@ export default {
Copy_Permalink: 'Copy Permalink',
Create_account: 'Create an account',
Create_Channel: 'Create Channel',
Created_snippet: 'Created a snippet',
Create_a_new_workspace: 'Create a new workspace',
Create: 'Create',
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_mentioned_messages: 'No mentioned messages',
No_pinned_messages: 'No pinned messages',
No_results_found: 'No results found',
No_snippeted_messages: 'No snippeted messages',
No_starred_messages: 'No starred messages',
No_announcement_provided: 'No announcement provided.',

View File

@ -138,6 +138,7 @@ export default {
Copy_Permalink: 'Copiar Link-Permanente',
Create_account: 'Criar conta',
Create_Channel: 'Criar Canal',
Created_snippet: 'Criou um snippet',
Create_a_new_workspace: 'Criar nova área de trabalho',
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.',
@ -212,6 +213,7 @@ export default {
No_files: 'Não há arquivos',
No_mentioned_messages: 'Não há menções',
No_pinned_messages: 'Não há mensagens fixadas',
No_results_found: 'Nenhum resultado encontrado',
No_snippeted_messages: 'Não há trechos de mensagens',
No_starred_messages: 'Não há mensagens favoritas',
No_announcement_provided: 'Sem anúncio.',

View File

@ -6,6 +6,19 @@ import database from '../realm';
import log from '../../utils/log';
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 };
if (latest) {
params = { ...params, latest: new Date(latest).toISOString() };

View File

@ -14,11 +14,7 @@ import {
} from '../actions/login';
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
import { setActiveUser } from '../actions/activeUsers';
import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
import { snippetedMessagesReceived } from '../actions/snippetedMessages';
import { roomFilesReceived } from '../actions/roomFiles';
import { someoneTyping, roomMessageReceived } from '../actions/room';
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) => {
if (ddpMessage.msg === 'added') {
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) => {
this.roles = this.roles || {};
@ -678,7 +557,7 @@ const RocketChat = {
async getRoomMember(rid, currentUserId) {
try {
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) {
return Promise.reject(error);
}
@ -692,8 +571,8 @@ const RocketChat = {
leaveRoom(roomId, t) {
return SDK.api.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId });
},
eraseRoom(rid) {
return call('eraseRoom', rid);
eraseRoom(roomId, t) {
return SDK.api.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId });
},
toggleMuteUserInRoom(rid, username, mute) {
if (mute) {
@ -701,17 +580,17 @@ const RocketChat = {
}
return call('unmuteUserInRoom', { rid, username });
},
toggleArchiveRoom(rid, archive) {
toggleArchiveRoom(roomId, t, 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) {
return call('saveRoomSettings', rid, params);
},
saveUserProfile(params, customFields) {
return call('saveUserProfile', params, customFields);
saveUserProfile(data) {
return SDK.api.post('users.updateOwnBasicInfo', { data });
},
saveUserPreferences(params) {
return call('saveUserPreferences', params);
@ -719,9 +598,6 @@ const RocketChat = {
saveNotificationSettings(roomId, notifications) {
return SDK.api.post('rooms.saveNotification', { roomId, notifications });
},
messageSearch(text, rid, limit) {
return call('messageSearch', text, rid, limit);
},
addUsersToRoom(rid) {
let { users } = reduxStore.getState().selectedUsers;
users = users.map(u => u.name);
@ -756,8 +632,8 @@ const RocketChat = {
getAvatarSuggestion() {
return call('getAvatarSuggestion');
},
resetAvatar() {
return call('resetAvatar');
resetAvatar(userId) {
return SDK.api.post('users.resetAvatar', { userId });
},
setAvatarFromService({ data, contentType = '', service = null }) {
return call('setAvatarFromService', data, contentType, service);
@ -804,6 +680,30 @@ const RocketChat = {
c: 'channels', d: 'im', p: 'groups'
};
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 activeUsers from './activeUsers';
import roles from './roles';
import starredMessages from './starredMessages';
import pinnedMessages from './pinnedMessages';
import mentionedMessages from './mentionedMessages';
import snippetedMessages from './snippetedMessages';
import roomFiles from './roomFiles';
import sortPreferences from './sortPreferences';
export default combineReducers({
@ -33,10 +29,6 @@ export default combineReducers({
customEmojis,
activeUsers,
roles,
starredMessages,
pinnedMessages,
mentionedMessages,
snippetedMessages,
roomFiles,
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 init from './init';
import state from './state';
import starredMessages from './starredMessages';
import pinnedMessages from './pinnedMessages';
import mentionedMessages from './mentionedMessages';
import snippetedMessages from './snippetedMessages';
import roomFiles from './roomFiles';
import deepLinking from './deepLinking';
const root = function* root() {
@ -22,11 +18,7 @@ const root = function* root() {
messages(),
selectServer(),
state(),
starredMessages(),
pinnedMessages(),
mentionedMessages(),
snippetedMessages(),
roomFiles(),
deepLinking()
]);
};

View File

@ -33,7 +33,8 @@ const handleLoginRequest = function* handleLoginRequest({ credentials }) {
username: data.me.username,
name: data.me.name,
language: data.me.language,
status: data.me.status
status: data.me.status,
customFields: data.me.customFields
};
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 I18n from '../i18n';
const eraseRoom = rid => RocketChat.eraseRoom(rid);
let sub;
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 }) {
try {
sub.stop();
yield RocketChat.leaveRoom(rid, t);
yield goRoomsListAndDelete(rid);
const result = yield RocketChat.leaveRoom(rid, t);
if (result.success) {
yield Navigation.popToRoot('RoomsListView');
}
} catch (e) {
if (e.data && e.data.errorType === 'error-you-are-last-owner') {
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 {
sub.stop();
yield eraseRoom(rid);
yield goRoomsListAndDelete(rid, 'erase');
const result = yield RocketChat.eraseRoom(rid, t);
if (result.success) {
yield Navigation.popToRoot('RoomsListView');
}
} catch (e) {
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 { connect } from 'react-redux';
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 styles from './styles';
import Message from '../../containers/message';
import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions';
import RocketChat from '../../lib/rocketchat';
import database from '../../lib/realm';
@connect(state => ({
messages: state.mentionedMessages.messages,
ready: state.mentionedMessages.ready,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
customEmojis: state.customEmojis,
user: {
id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token
}
}), dispatch => ({
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessagesAction(rid, limit)),
closeMentionedMessages: () => dispatch(closeMentionedMessagesAction())
}))
/** @extends React.Component */
export default class MentionedMessagesView extends LoggedView {
@ -41,53 +40,57 @@ export default class MentionedMessagesView extends LoggedView {
static propTypes = {
rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object,
openMentionedMessages: PropTypes.func,
closeMentionedMessages: PropTypes.func
baseUrl: PropTypes.string,
customEmojis: PropTypes.object
}
constructor(props) {
super('MentionedMessagesView', props);
super('StarredMessagesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = {
loading: true,
loadingMore: false
loading: false,
room: this.rooms[0],
messages: []
};
}
componentDidMount() {
this.limit = 20;
this.load();
}
componentWillReceiveProps(nextProps) {
const { ready } = this.props;
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
shouldComponentUpdate(nextProps, nextState) {
return !equal(this.state, nextState);
}
componentWillUnmount() {
const { closeMentionedMessages } = this.props;
closeMentionedMessages();
}
load = () => {
const { openMentionedMessages, rid } = this.props;
openMentionedMessages(rid, this.limit);
}
moreData = () => {
const { loadingMore } = this.state;
const { messages } = this.props;
if (messages.length < this.limit) {
load = async() => {
const {
messages, total, loading, room
} = this.state;
const { user } = this.props;
if (messages.length === total || loading) {
return;
}
if (!loadingMore) {
this.setState({ loadingMore: true });
this.limit += 20;
this.load();
this.setState({ loading: true });
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 }) => {
const { user } = this.props;
const { user, customEmojis, baseUrl } = this.props;
return (
<Message
item={item}
style={styles.message}
reactions={item.reactions}
customEmojis={customEmojis}
baseUrl={baseUrl}
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() {
const { loading, loadingMore } = this.state;
const { messages, ready } = this.props;
const { messages, loading } = this.state;
if (ready && messages.length === 0) {
if (!loading && messages.length === 0) {
return this.renderEmpty();
}
@ -125,9 +133,8 @@ export default class MentionedMessagesView extends LoggedView {
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
onEndReached={this.moreData}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
onEndReached={this.load}
ListFooterComponent={loading ? <RCActivityIndicator /> : null}
/>
</SafeAreaView>
);

View File

@ -4,32 +4,29 @@ import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux';
import ActionSheet from 'react-native-actionsheet';
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 styles from './styles';
import Message from '../../containers/message';
import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions';
import RocketChat from '../../lib/rocketchat';
import database from '../../lib/realm';
const PIN_INDEX = 0;
const CANCEL_INDEX = 1;
const options = [I18n.t('Unpin'), I18n.t('Cancel')];
@connect(state => ({
messages: state.pinnedMessages.messages,
ready: state.pinnedMessages.ready,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
customEmojis: state.customEmojis,
user: {
id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username,
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 */
export default class PinnedMessagesView extends LoggedView {
@ -48,38 +45,27 @@ export default class PinnedMessagesView extends LoggedView {
static propTypes = {
rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object,
openPinnedMessages: PropTypes.func,
closePinnedMessages: PropTypes.func,
togglePinRequest: PropTypes.func
baseUrl: PropTypes.string,
customEmojis: PropTypes.object
}
constructor(props) {
super('PinnedMessagesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = {
message: {},
loading: true,
loadingMore: false
loading: false,
room: this.rooms[0],
messages: []
};
}
componentDidMount() {
this.limit = 20;
this.load();
}
componentWillReceiveProps(nextProps) {
const { ready } = this.props;
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
}
componentWillUnmount() {
const { closePinnedMessages } = this.props;
closePinnedMessages();
shouldComponentUpdate(nextProps, nextState) {
return !equal(this.state, nextState);
}
onLongPress = (message) => {
@ -90,33 +76,51 @@ export default class PinnedMessagesView extends LoggedView {
}
handleActionPress = (actionIndex) => {
const { message } = this.state;
const { togglePinRequest } = this.props;
switch (actionIndex) {
case PIN_INDEX:
togglePinRequest(message);
this.unPin();
break;
default:
break;
}
}
load = () => {
const { openPinnedMessages, rid } = this.props;
openPinnedMessages(rid, this.limit);
unPin = async() => {
const { message } = this.state;
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 = () => {
const { loadingMore } = this.state;
const { messages } = this.props;
if (messages.length < this.limit) {
load = async() => {
const {
messages, total, loading, room
} = this.state;
if (messages.length === total || loading) {
return;
}
if (!loadingMore) {
this.setState({ loadingMore: true });
this.limit += 20;
this.load();
this.setState({ loading: true });
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 }) => {
const { user } = this.props;
const { user, customEmojis, baseUrl } = this.props;
return (
<Message
item={item}
style={styles.message}
reactions={item.reactions}
customEmojis={customEmojis}
baseUrl={baseUrl}
user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
onLongPress={this.onLongPress}
author={item.u}
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() {
const { loading, loadingMore } = this.state;
const { messages, ready } = this.props;
const { messages, loading } = this.state;
if (ready && messages.length === 0) {
if (!loading && messages.length === 0) {
return this.renderEmpty();
}
@ -155,9 +164,8 @@ export default class PinnedMessagesView extends LoggedView {
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
onEndReached={this.moreData}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
onEndReached={this.load}
ListFooterComponent={loading ? <RCActivityIndicator /> : null}
/>
<ActionSheet
ref={o => this.actionSheet = o}

View File

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

View File

@ -68,7 +68,9 @@ export default class RoomActionsView extends LoggedView {
this.state = {
room: this.rooms[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);
}
@ -310,6 +315,20 @@ export default class RoomActionsView extends LoggedView {
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 = () => {
const { room } = this.state;
const { rid, blocker } = room;

View File

@ -3,26 +3,25 @@ import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux';
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 styles from './styles';
import Message from '../../containers/message';
import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions';
import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
@connect(state => ({
messages: state.roomFiles.messages,
ready: state.roomFiles.ready,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
customEmojis: state.customEmojis,
user: {
id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token
}
}), dispatch => ({
openRoomFiles: (rid, limit) => dispatch(openRoomFilesAction(rid, limit)),
closeRoomFiles: () => dispatch(closeRoomFilesAction())
}))
/** @extends React.Component */
export default class RoomFilesView extends LoggedView {
@ -41,53 +40,51 @@ export default class RoomFilesView extends LoggedView {
static propTypes = {
rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object,
openRoomFiles: PropTypes.func,
closeRoomFiles: PropTypes.func
baseUrl: PropTypes.string,
customEmojis: PropTypes.object
}
constructor(props) {
super('RoomFilesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = {
loading: true,
loadingMore: false
loading: false,
room: this.rooms[0],
messages: []
};
}
componentDidMount() {
this.limit = 20;
this.load();
}
componentWillReceiveProps(nextProps) {
const { ready } = this.props;
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
shouldComponentUpdate(nextProps, nextState) {
return !equal(this.state, nextState);
}
componentWillUnmount() {
const { closeRoomFiles } = this.props;
closeRoomFiles();
}
load = () => {
const { openRoomFiles, rid } = this.props;
openRoomFiles(rid, this.limit);
}
moreData = () => {
const { loadingMore } = this.state;
const { messages } = this.props;
if (messages.length < this.limit) {
load = async() => {
const {
messages, total, loading, room
} = this.state;
if (messages.length === total || loading) {
return;
}
if (!loadingMore) {
this.setState({ loadingMore: true });
this.limit += 20;
this.load();
this.setState({ loading: true });
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 }) => {
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 (
<Message
item={item}
style={styles.message}
reactions={item.reactions}
customEmojis={customEmojis}
baseUrl={baseUrl}
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() {
const { messages, ready } = this.props;
if (ready && messages.length === 0) {
const { messages, loading } = this.state;
if (!loading && messages.length === 0) {
return this.renderEmpty();
}
const { loading, loadingMore } = this.state;
return (
<SafeAreaView style={styles.list} testID='room-files-view' forceInset={{ bottom: 'never' }}>
<FlatList
@ -125,9 +145,8 @@ export default class RoomFilesView extends LoggedView {
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
onEndReached={this.moreData}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
onEndReached={this.load}
ListFooterComponent={loading ? <RCActivityIndicator /> : null}
/>
</SafeAreaView>
);

View File

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

View File

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

View File

@ -133,6 +133,12 @@ export default class RoomView extends LoggedView {
return true;
} else if (room.f !== nextState.room.f) {
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) {
return true;
} else if (joined !== nextState.joined) {
@ -156,17 +162,21 @@ export default class RoomView extends LoggedView {
const { componentId, appState } = this.props;
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, {
topBar: {
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
}]
rightButtons
}
});
} else if (appState === 'foreground' && appState !== prevProps.appState) {
@ -375,14 +385,14 @@ export default class RoomView extends LoggedView {
}
if (room.archived || this.isReadOnly()) {
return (
<View style={styles.readOnly}>
<View style={styles.readOnly} key='room-view-read-only'>
<Text>{I18n.t('This_room_is_read_only')}</Text>
</View>
);
}
if (this.isBlocked()) {
return (
<View style={styles.blockedOrBlocker}>
<View style={styles.readOnly} key='room-view-block'>
<Text>{I18n.t('This_room_is_blocked')}</Text>
</View>
);

View File

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

View File

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

View File

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

View File

@ -105,7 +105,7 @@ export default class SnippetedMessagesView extends LoggedView {
style={styles.message}
reactions={item.reactions}
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 ActionSheet from 'react-native-actionsheet';
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 styles from './styles';
import Message from '../../containers/message';
import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import { DEFAULT_HEADER } from '../../constants/headerOptions';
import RocketChat from '../../lib/rocketchat';
import database from '../../lib/realm';
const STAR_INDEX = 0;
const CANCEL_INDEX = 1;
const options = [I18n.t('Unstar'), I18n.t('Cancel')];
@connect(state => ({
messages: state.starredMessages.messages,
ready: state.starredMessages.ready,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
customEmojis: state.customEmojis,
user: {
id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username,
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 */
export default class StarredMessagesView extends LoggedView {
@ -48,38 +45,27 @@ export default class StarredMessagesView extends LoggedView {
static propTypes = {
rid: PropTypes.string,
messages: PropTypes.array,
ready: PropTypes.bool,
user: PropTypes.object,
openStarredMessages: PropTypes.func,
closeStarredMessages: PropTypes.func,
toggleStarRequest: PropTypes.func
baseUrl: PropTypes.string,
customEmojis: PropTypes.object
}
constructor(props) {
super('StarredMessagesView', props);
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
this.state = {
message: {},
loading: true,
loadingMore: false
loading: false,
room: this.rooms[0],
messages: []
};
}
componentDidMount() {
this.limit = 20;
this.load();
}
componentWillReceiveProps(nextProps) {
const { ready } = this.props;
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
}
componentWillUnmount() {
const { closeStarredMessages } = this.props;
closeStarredMessages();
shouldComponentUpdate(nextProps, nextState) {
return !equal(this.state, nextState);
}
onLongPress = (message) => {
@ -90,33 +76,57 @@ export default class StarredMessagesView extends LoggedView {
}
handleActionPress = (actionIndex) => {
const { message } = this.state;
const { toggleStarRequest } = this.props;
switch (actionIndex) {
case STAR_INDEX:
toggleStarRequest(message);
this.unStar();
break;
default:
break;
}
}
load = () => {
const { rid, openStarredMessages } = this.props;
openStarredMessages(rid, this.limit);
unStar = async() => {
const { message } = this.state;
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 = () => {
const { loadingMore } = this.state;
const { messages } = this.props;
if (messages.length < this.limit) {
load = async() => {
const {
messages, total, loading, room
} = this.state;
const { user } = this.props;
if (messages.length === total || loading) {
return;
}
if (!loadingMore) {
this.setState({ loadingMore: true });
this.limit += 20;
this.load();
this.setState({ loading: true });
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 }) => {
const { user } = this.props;
const { user, customEmojis, baseUrl } = this.props;
return (
<Message
item={item}
style={styles.message}
reactions={item.reactions}
customEmojis={customEmojis}
baseUrl={baseUrl}
user={user}
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
onLongPress={this.onLongPress}
author={item.u}
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() {
const { loading, loadingMore } = this.state;
const { messages, ready } = this.props;
const { messages, loading } = this.state;
if (ready && messages.length === 0) {
if (!loading && messages.length === 0) {
return this.renderEmpty();
}
@ -155,9 +170,8 @@ export default class StarredMessagesView extends LoggedView {
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
onEndReached={this.moreData}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
onEndReached={this.load}
ListFooterComponent={loading ? <RCActivityIndicator /> : null}
/>
<ActionSheet
ref={o => this.actionSheet = o}