diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx index 466a70ce51..b5e80fbcc9 100644 --- a/app/containers/MessageActions/index.tsx +++ b/app/containers/MessageActions/index.tsx @@ -6,6 +6,9 @@ import { connect } from 'react-redux'; import dayjs from '../../lib/dayjs'; import database from '../../lib/database'; import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription'; +import { getMessageById } from '../../lib/database/services/Message'; +import protectedFunction from '../../lib/methods/helpers/protectedFunction'; +import { registerOptimisticUpdate } from '../../lib/methods/helpers/optimisticUpdates'; import I18n from '../../i18n'; import log, { logEvent } from '../../lib/methods/helpers/log'; import Navigation from '../../lib/navigation/appNavigation'; @@ -318,10 +321,45 @@ const MessageActions = React.memo( }; const handlePin = async (message: TAnyMessageModel) => { + const currentPinned = Boolean(message.pinned); + const willPin = !currentPinned; logEvent(events.ROOM_MSG_ACTION_PIN); + try { - await togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage + const db = database.active; + const messageRecord = await getMessageById(message.id); + if (messageRecord) { + await db.write(async () => { + await messageRecord.update( + protectedFunction((m: any) => { + m.pinned = willPin; + }) + ); + }); + registerOptimisticUpdate(message.id, { pinned: willPin }); + } + } catch (optimisticError) { + // Do nothing + } + + try { + await togglePinMessage(message.id, currentPinned); // TODO: reevaluate `message.pinned` type on IMessage } catch (e) { + try { + const db = database.active; + const messageRecord = await getMessageById(message.id); + if (messageRecord) { + await db.write(async () => { + await messageRecord.update( + protectedFunction((m: any) => { + m.pinned = currentPinned; + }) + ); + }); + } + } catch (revertError) { + // Do nothing + } logEvent(events.ROOM_MSG_ACTION_PIN_F); log(e); } diff --git a/app/containers/message/Message.tsx b/app/containers/message/Message.tsx index ce32c546e6..8f811872ca 100644 --- a/app/containers/message/Message.tsx +++ b/app/containers/message/Message.tsx @@ -154,6 +154,8 @@ const Message = React.memo((props: IMessageTouchable & IMessage) => { : `${user} ${hour} ${translated} ${label}. ${encryptedMessageLabel} ${readReceipt}`; }; + const showRightIcons = !props.isHeader; + if (props.isThreadReply || props.isThreadSequential || props.isInfo || props.isIgnored) { const thread = props.isThreadReply ? : null; // Prevent misalignment of info when the font size is increased. @@ -195,7 +197,7 @@ const Message = React.memo((props: IMessageTouchable & IMessage) => { - {!props.isHeader ? ( + {showRightIcons ? ( = new Map(); +const CLEANUP_THRESHOLD = 10000; + +const cleanupStaleUpdates = (maxAge: number = CLEANUP_THRESHOLD) => { + const now = Date.now(); + for (const [messageId, update] of optimisticUpdates.entries()) { + const age = now - update.timestamp; + if (age >= maxAge) { + optimisticUpdates.delete(messageId); + } + } +}; + +export const registerOptimisticUpdate = (messageId: string, update: { pinned?: boolean }) => { + cleanupStaleUpdates(); + optimisticUpdates.set(messageId, { + ...update, + timestamp: Date.now() + }); +}; + +export const getOptimisticUpdate = (messageId: string): IOptimisticUpdate | undefined => { + cleanupStaleUpdates(); + return optimisticUpdates.get(messageId); +}; + +export const clearOptimisticUpdate = (messageId: string) => { + optimisticUpdates.delete(messageId); +}; + +export const isRecentOptimisticUpdate = (messageId: string, maxAge: number = 2000): boolean => { + cleanupStaleUpdates(); + const update = optimisticUpdates.get(messageId); + if (!update) return false; + const age = Date.now() - update.timestamp; + return age < maxAge; +}; diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index a0ce1310b2..93083284c5 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -6,6 +6,7 @@ import { Q } from '@nozbe/watermelondb'; import log from '../helpers/log'; import protectedFunction from '../helpers/protectedFunction'; import buildMessage from '../helpers/buildMessage'; +import { getOptimisticUpdate, isRecentOptimisticUpdate, clearOptimisticUpdate } from '../helpers/optimisticUpdates'; import database from '../../database'; import { getMessageById } from '../../database/services/Message'; import { getThreadById } from '../../database/services/Thread'; @@ -281,7 +282,23 @@ export default class RoomSubscription { batch.push( messageRecord.prepareUpdate( protectedFunction((m: TMessageModel) => { - Object.assign(m, message); + const optimisticUpdate = getOptimisticUpdate(message._id); + const isRecentOptimistic = isRecentOptimisticUpdate(message._id, 2000); + + const { pinned: _pinned, ...restMessage } = message; + Object.assign(m, restMessage); + + if (message.pinned !== undefined) { + if (isRecentOptimistic && optimisticUpdate?.pinned !== undefined) { + m.pinned = optimisticUpdate.pinned; + } else { + m.pinned = message.pinned; + } + } + + if (isRecentOptimistic) { + clearOptimisticUpdate(message._id); + } }) ) );