Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions app/containers/MessageActions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -306,12 +309,47 @@ const MessageActions = React.memo(
}
};

const handleStar = async (messageId: string, starred: boolean) => {
logEvent(starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
const handleStar = async (message: TAnyMessageModel) => {
const currentStarred = Boolean(message.starred);
const willStar = !currentStarred;
logEvent(events.ROOM_MSG_ACTION_STAR);

try {
await toggleStarMessage(messageId, starred);
EventEmitter.emit(LISTENER, { message: starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
const db = database.active;
const messageRecord = await getMessageById(message.id);
if (messageRecord) {
await db.write(async () => {
await messageRecord.update(
protectedFunction((m: any) => {
m.starred = willStar;
})
);
});
registerOptimisticUpdate(message.id, { starred: willStar });
}
} catch (optimisticError) {
// Do nothing
}

try {
await toggleStarMessage(message.id, currentStarred);
EventEmitter.emit(LISTENER, { message: willStar ? I18n.t('Message_starred') : I18n.t('Message_unstarred') });
} 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.starred = currentStarred;
})
);
});
}
} catch (revertError) {
// Do nothing
}
logEvent(events.ROOM_MSG_ACTION_STAR_F);
log(e);
}
Expand Down Expand Up @@ -506,7 +544,7 @@ const MessageActions = React.memo(
options.push({
title: I18n.t(message.starred ? 'Unstar' : 'Star'),
icon: message.starred ? 'star-filled' : 'star',
onPress: () => handleStar(message.id, message.starred || false)
onPress: () => handleStar(message)
});
}

Expand Down
13 changes: 13 additions & 0 deletions app/containers/message/Components/RightIcons/Starred.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

import { CustomIcon } from '../../../CustomIcon';
import styles from '../../styles';

const Starred = ({ starred, testID }: { starred?: boolean; testID?: string }): React.ReactElement | null => {
'use memo';

if (starred) return <CustomIcon testID={testID} name='star-filled' size={16} style={styles.rightIcons} />;
return null;
};

export default Starred;
6 changes: 5 additions & 1 deletion app/containers/message/Components/RightIcons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Encrypted from './Encrypted';
import MessageError from './MessageError';
import Pinned from './Pinned';
import ReadReceipt from './ReadReceipt';
import Starred from './Starred';
import Translated from './Translated';

const styles = StyleSheet.create({
Expand All @@ -24,6 +25,7 @@ interface IRightIcons {
hasError: boolean;
isTranslated: boolean;
pinned?: boolean;
starred?: boolean;
}

const RightIcons = ({
Expand All @@ -34,13 +36,15 @@ const RightIcons = ({
isReadReceiptEnabled,
unread,
isTranslated,
pinned
pinned,
starred
}: IRightIcons): React.ReactElement => {
'use memo';

return (
<View style={styles.actionIcons}>
<Pinned pinned={pinned} testID={`${msg}-pinned`} />
<Starred starred={starred} testID={`${msg}-starred`} />
<Encrypted type={type} />
<Edited testID={`${msg}-edited`} isEdited={isEdited} />
<MessageError hasError={hasError} />
Expand Down
1 change: 1 addition & 0 deletions app/containers/message/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ const Message = React.memo((props: IMessageTouchable & IMessage) => {
isReadReceiptEnabled={props.isReadReceiptEnabled}
unread={props.unread}
pinned={props.pinned}
starred={props.starred}
isTranslated={props.isTranslated}
/>
) : null}
Expand Down
2 changes: 2 additions & 0 deletions app/containers/message/User.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ interface IMessageUser {
isReadReceiptEnabled?: boolean;
unread?: boolean;
pinned?: boolean;
starred?: boolean;
isTranslated: boolean;
}

Expand Down Expand Up @@ -129,6 +130,7 @@ const User = React.memo(
isReadReceiptEnabled={props.isReadReceiptEnabled}
unread={props.unread}
pinned={props.pinned}
starred={props.starred}
isTranslated={isTranslated}
/>
</View>
Expand Down
4 changes: 3 additions & 1 deletion app/containers/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
replies,
md,
comment,
pinned
pinned,
starred
} = item;

let message = msg;
Expand Down Expand Up @@ -505,6 +506,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
isBeingEdited={isBeingEdited}
isPreview={isPreview}
pinned={pinned}
starred={starred}
autoTranslateLanguage={autoTranslateLanguage}
/>
<MessageSeparator ts={dateSeparator} unread={showUnreadSeparator} />
Expand Down
1 change: 1 addition & 0 deletions app/containers/message/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface IMessageContent {
isHeader: boolean;
isTranslated: boolean;
pinned?: boolean;
starred?: boolean;
}

export interface IMessageEmoji {
Expand Down
43 changes: 43 additions & 0 deletions app/lib/methods/helpers/optimisticUpdates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
interface IOptimisticUpdate {
pinned?: boolean;
starred?: boolean;
timestamp: number;
}

const optimisticUpdates: Map<string, IOptimisticUpdate> = 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; starred?: 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;
};
23 changes: 22 additions & 1 deletion app/lib/methods/subscriptions/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { readMessages } from '../readMessages';
import { loadMissedMessages } from '../loadMissedMessages';
import { updateLastOpen } from '../updateLastOpen';
import markMessagesRead from '../helpers/markMessagesRead';
import { getOptimisticUpdate, isRecentOptimisticUpdate } from '../helpers/optimisticUpdates';

export default class RoomSubscription {
private rid: string;
Expand Down Expand Up @@ -281,7 +282,27 @@ 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, starred: _starred, ...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 (message.starred !== undefined) {
if (isRecentOptimistic && optimisticUpdate?.starred !== undefined) {
m.starred = optimisticUpdate.starred;
} else {
m.starred = message.starred;
}
}
})
)
);
Expand Down