diff --git a/app/containers/message/Content.js b/app/containers/message/Content.js index e358f2b2a..6d2d9b5d3 100644 --- a/app/containers/message/Content.js +++ b/app/containers/message/Content.js @@ -1,6 +1,7 @@ import React from 'react'; import { Text, View } from 'react-native'; import PropTypes from 'prop-types'; +import equal from 'deep-equal'; import I18n from '../../i18n'; import styles from './styles'; @@ -42,7 +43,24 @@ const Content = React.memo((props) => { {content} ); -}, (prevProps, nextProps) => prevProps.isTemp === nextProps.isTemp && prevProps.msg === nextProps.msg && prevProps.theme === nextProps.theme); +}, (prevProps, nextProps) => { + if (prevProps.isTemp !== nextProps.isTemp) { + return false; + } + if (prevProps.msg !== nextProps.msg) { + return false; + } + if (prevProps.theme !== nextProps.theme) { + return false; + } + if (!equal(prevProps.mentions, nextProps.mentions)) { + return false; + } + if (!equal(prevProps.channels, nextProps.channels)) { + return false; + } + return true; +}); Content.propTypes = { isTemp: PropTypes.bool, diff --git a/app/lib/methods/sendMessage.js b/app/lib/methods/sendMessage.js index 0d9fe5973..2b9405f16 100644 --- a/app/lib/methods/sendMessage.js +++ b/app/lib/methods/sendMessage.js @@ -5,7 +5,7 @@ import database from '../database'; import log from '../../utils/log'; import random from '../../utils/random'; -const changeMessageStatus = async(id, tmid, status) => { +const changeMessageStatus = async(id, tmid, status, message) => { const db = database.active; const msgCollection = db.collections.get('messages'); const threadMessagesCollection = db.collections.get('thread_messages'); @@ -14,6 +14,8 @@ const changeMessageStatus = async(id, tmid, status) => { successBatch.push( messageRecord.prepareUpdate((m) => { m.status = status; + m.mentions = message.mentions; + m.channels = message.channels; }) ); @@ -22,6 +24,8 @@ const changeMessageStatus = async(id, tmid, status) => { successBatch.push( threadMessageRecord.prepareUpdate((tm) => { tm.status = status; + tm.mentions = message.mentions; + tm.channels = message.channels; }) ); } @@ -42,12 +46,12 @@ export async function sendMessageCall(message) { try { const sdk = this.shareSDK || this.sdk; // RC 0.60.0 - await sdk.post('chat.sendMessage', { + const result = await sdk.post('chat.sendMessage', { message: { _id, rid, msg, tmid } }); - await changeMessageStatus(_id, tmid, messagesStatus.SENT); + await changeMessageStatus(_id, tmid, messagesStatus.SENT, result.message); } catch (e) { await changeMessageStatus(_id, tmid, messagesStatus.ERROR); } diff --git a/app/lib/methods/subscriptions/room.js b/app/lib/methods/subscriptions/room.js index ccf944f8d..ff4835061 100644 --- a/app/lib/methods/subscriptions/room.js +++ b/app/lib/methods/subscriptions/room.js @@ -11,10 +11,17 @@ import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actio import debounce from '../../../utils/debounce'; import RocketChat from '../../rocketchat'; +const WINDOW_TIME = 1000; + export default class RoomSubscription { constructor(rid) { this.rid = rid; this.isAlive = true; + this.timer = null; + this.queue = {}; + this.messagesBatch = {}; + this.threadsBatch = {}; + this.threadMessagesBatch = {}; } subscribe = async() => { @@ -49,6 +56,9 @@ export default class RoomSubscription { this.removeListener(this.notifyRoomListener); this.removeListener(this.messageReceivedListener); reduxStore.dispatch(clearUserTyping()); + if (this.timer) { + clearTimeout(this.timer); + } } removeListener = async(promise) => { @@ -131,15 +141,13 @@ export default class RoomSubscription { RocketChat.readMessages(this.rid, lastOpen); }, 300); - handleMessageReceived = protectedFunction((ddpMessage) => { - const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0])); - const lastOpen = new Date(); - if (this.rid !== message.rid) { - return; - } - InteractionManager.runAfterInteractions(async() => { + updateMessage = message => ( + new Promise(async(resolve) => { + if (this.rid !== message.rid) { + return; + } + const db = database.active; - const batch = []; const msgCollection = db.collections.get('messages'); const threadsCollection = db.collections.get('threads'); const threadMessagesCollection = db.collections.get('thread_messages'); @@ -154,22 +162,17 @@ export default class RoomSubscription { // Do nothing } if (messageRecord) { - try { - const update = messageRecord.prepareUpdate((m) => { - Object.assign(m, message); - }); - batch.push(update); - } catch (e) { - console.log(e); - } + const update = messageRecord.prepareUpdate((m) => { + Object.assign(m, message); + }); + this._messagesBatch[message._id] = update; } else { - batch.push( - msgCollection.prepareCreate(protectedFunction((m) => { - m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); - m.subscription.id = this.rid; - Object.assign(m, message); - })) - ); + const create = msgCollection.prepareCreate(protectedFunction((m) => { + m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); + m.subscription.id = this.rid; + Object.assign(m, message); + })); + this._messagesBatch[message._id] = create; } // Create or update thread @@ -181,19 +184,17 @@ export default class RoomSubscription { } if (threadRecord) { - batch.push( - threadRecord.prepareUpdate(protectedFunction((t) => { - Object.assign(t, message); - })) - ); + const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => { + Object.assign(t, message); + })); + this._threadsBatch[message._id] = updateThread; } else { - batch.push( - threadsCollection.prepareCreate(protectedFunction((t) => { - t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema); - t.subscription.id = this.rid; - Object.assign(t, message); - })) - ); + const createThread = threadsCollection.prepareCreate(protectedFunction((t) => { + t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema); + t.subscription.id = this.rid; + Object.assign(t, message); + })); + this._threadsBatch[message._id] = createThread; } } @@ -206,35 +207,75 @@ export default class RoomSubscription { } if (threadMessageRecord) { - batch.push( - threadMessageRecord.prepareUpdate(protectedFunction((tm) => { - Object.assign(tm, message); - tm.rid = message.tmid; - delete tm.tmid; - })) - ); + const updateThreadMessage = threadMessageRecord.prepareUpdate(protectedFunction((tm) => { + Object.assign(tm, message); + tm.rid = message.tmid; + delete tm.tmid; + })); + this._threadMessagesBatch[message._id] = updateThreadMessage; } else { - batch.push( - threadMessagesCollection.prepareCreate(protectedFunction((tm) => { - tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema); - Object.assign(tm, message); - tm.subscription.id = this.rid; - tm.rid = message.tmid; - delete tm.tmid; - })) - ); + const createThreadMessage = threadMessagesCollection.prepareCreate(protectedFunction((tm) => { + tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema); + Object.assign(tm, message); + tm.subscription.id = this.rid; + tm.rid = message.tmid; + delete tm.tmid; + })); + this._threadMessagesBatch[message._id] = createThreadMessage; } } - this.read(lastOpen); + return resolve(); + }) + ) - try { - await db.action(async() => { - await db.batch(...batch); - }); - } catch (e) { - log(e); - } - }); - }); + handleMessageReceived = (ddpMessage) => { + if (!this.timer) { + this.timer = setTimeout(async() => { + // copy variables values to local and clean them + const _lastOpen = this.lastOpen; + const _queue = Object.keys(this.queue).map(key => this.queue[key]); + this._messagesBatch = this.messagesBatch; + this._threadsBatch = this.threadsBatch; + this._threadMessagesBatch = this.threadMessagesBatch; + this.queue = {}; + this.messagesBatch = {}; + this.threadsBatch = {}; + this.threadMessagesBatch = {}; + this.timer = null; + + for (let i = 0; i < _queue.length; i += 1) { + try { + // eslint-disable-next-line no-await-in-loop + await this.updateMessage(_queue[i]); + } catch (e) { + log(e); + } + } + + try { + const db = database.active; + await db.action(async() => { + await db.batch( + ...Object.values(this._messagesBatch), + ...Object.values(this._threadsBatch), + ...Object.values(this._threadMessagesBatch) + ); + }); + + this.read(_lastOpen); + } catch (e) { + log(e); + } + + // Clean local variables + this._messagesBatch = {}; + this._threadsBatch = {}; + this._threadMessagesBatch = {}; + }, WINDOW_TIME); + } + this.lastOpen = new Date(); + const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0])); + this.queue[message._id] = message; + }; } diff --git a/app/lib/methods/updateMessages.js b/app/lib/methods/updateMessages.js index 77a0ced43..350ece501 100644 --- a/app/lib/methods/updateMessages.js +++ b/app/lib/methods/updateMessages.js @@ -74,17 +74,29 @@ export default function updateMessages({ rid, update = [], remove = [] }) { // Update msgsToUpdate = msgsToUpdate.map((message) => { const newMessage = update.find(m => m._id === message.id); + if (message._hasPendingUpdate) { + console.log(message); + return; + } return message.prepareUpdate(protectedFunction((m) => { Object.assign(m, newMessage); })); }); threadsToUpdate = threadsToUpdate.map((thread) => { + if (thread._hasPendingUpdate) { + console.log(thread); + return; + } const newThread = allThreads.find(t => t._id === thread.id); return thread.prepareUpdate(protectedFunction((t) => { Object.assign(t, newThread); })); }); threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => { + if (threadMessage._hasPendingUpdate) { + console.log(threadMessage); + return; + } const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id); return threadMessage.prepareUpdate(protectedFunction((tm) => { Object.assign(tm, newThreadMessage); diff --git a/app/utils/layoutAnimation.js b/app/utils/layoutAnimation.js index 3b4292d35..b338c7a3d 100644 --- a/app/utils/layoutAnimation.js +++ b/app/utils/layoutAnimation.js @@ -5,6 +5,19 @@ import { isIOS } from './deviceInfo'; export const animateNextTransition = debounce(() => { if (isIOS) { - LayoutAnimation.easeInEaseOut(); + LayoutAnimation.configureNext({ + duration: 200, + create: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity + }, + update: { + type: LayoutAnimation.Types.easeInEaseOut + }, + delete: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity + } + }); } }, 200, true);