Timeline: Use messageChangeCounter to mark messages read less often
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
01a9d3d3ff
commit
7706f8c695
|
@ -533,6 +533,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
overrideProps.isIncomingMessageRequest === true
|
overrideProps.isIncomingMessageRequest === true
|
||||||
),
|
),
|
||||||
items: overrideProps.items || Object.keys(items),
|
items: overrideProps.items || Object.keys(items),
|
||||||
|
messageChangeCounter: 0,
|
||||||
scrollToIndex: overrideProps.scrollToIndex,
|
scrollToIndex: overrideProps.scrollToIndex,
|
||||||
scrollToIndexCounter: 0,
|
scrollToIndexCounter: 0,
|
||||||
totalUnseen: number('totalUnseen', overrideProps.totalUnseen || 0),
|
totalUnseen: number('totalUnseen', overrideProps.totalUnseen || 0),
|
||||||
|
|
|
@ -86,6 +86,7 @@ export type ContactSpoofingReviewPropType =
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
haveNewest: boolean;
|
haveNewest: boolean;
|
||||||
haveOldest: boolean;
|
haveOldest: boolean;
|
||||||
|
messageChangeCounter: number;
|
||||||
messageLoadingState?: TimelineMessageLoadingState;
|
messageLoadingState?: TimelineMessageLoadingState;
|
||||||
isNearBottom?: boolean;
|
isNearBottom?: boolean;
|
||||||
items: ReadonlyArray<string>;
|
items: ReadonlyArray<string>;
|
||||||
|
@ -647,12 +648,14 @@ export class Timeline extends React.Component<
|
||||||
): void {
|
): void {
|
||||||
const {
|
const {
|
||||||
items: oldItems,
|
items: oldItems,
|
||||||
|
messageChangeCounter: previousMessageChangeCounter,
|
||||||
messageLoadingState: previousMessageLoadingState,
|
messageLoadingState: previousMessageLoadingState,
|
||||||
} = prevProps;
|
} = prevProps;
|
||||||
const {
|
const {
|
||||||
discardMessages,
|
discardMessages,
|
||||||
id,
|
id,
|
||||||
items: newItems,
|
items: newItems,
|
||||||
|
messageChangeCounter,
|
||||||
messageLoadingState,
|
messageLoadingState,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -711,7 +714,8 @@ export class Timeline extends React.Component<
|
||||||
numberToKeepAtTop,
|
numberToKeepAtTop,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
if (previousMessageChangeCounter !== messageChangeCounter) {
|
||||||
this.markNewestBottomVisibleMessageRead();
|
this.markNewestBottomVisibleMessageRead();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,6 +244,7 @@ export type MessageLookupType = {
|
||||||
};
|
};
|
||||||
export type ConversationMessageType = {
|
export type ConversationMessageType = {
|
||||||
isNearBottom?: boolean;
|
isNearBottom?: boolean;
|
||||||
|
messageChangeCounter: number;
|
||||||
messageIds: Array<string>;
|
messageIds: Array<string>;
|
||||||
messageLoadingState?: undefined | TimelineMessageLoadingState;
|
messageLoadingState?: undefined | TimelineMessageLoadingState;
|
||||||
metrics: MessageMetricsType;
|
metrics: MessageMetricsType;
|
||||||
|
@ -2502,8 +2503,18 @@ export function reducer(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toIncrement = data.reactions?.length ? 1 : 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
messagesByConversation: {
|
||||||
|
...state.messagesByConversation,
|
||||||
|
[conversationId]: {
|
||||||
|
...existingConversation,
|
||||||
|
messageChangeCounter:
|
||||||
|
(existingConversation.messageChangeCounter || 0) + toIncrement,
|
||||||
|
},
|
||||||
|
},
|
||||||
messagesLookup: {
|
messagesLookup: {
|
||||||
...state.messagesLookup,
|
...state.messagesLookup,
|
||||||
[id]: {
|
[id]: {
|
||||||
|
@ -2582,6 +2593,7 @@ export function reducer(
|
||||||
messagesByConversation: {
|
messagesByConversation: {
|
||||||
...messagesByConversation,
|
...messagesByConversation,
|
||||||
[conversationId]: {
|
[conversationId]: {
|
||||||
|
messageChangeCounter: 0,
|
||||||
scrollToMessageId,
|
scrollToMessageId,
|
||||||
scrollToMessageCounter: existingConversation
|
scrollToMessageCounter: existingConversation
|
||||||
? existingConversation.scrollToMessageCounter + 1
|
? existingConversation.scrollToMessageCounter + 1
|
||||||
|
|
|
@ -828,6 +828,7 @@ export function _conversationMessagesSelector(
|
||||||
): TimelinePropsType {
|
): TimelinePropsType {
|
||||||
const {
|
const {
|
||||||
isNearBottom,
|
isNearBottom,
|
||||||
|
messageChangeCounter,
|
||||||
messageIds,
|
messageIds,
|
||||||
messageLoadingState,
|
messageLoadingState,
|
||||||
metrics,
|
metrics,
|
||||||
|
@ -860,6 +861,7 @@ export function _conversationMessagesSelector(
|
||||||
haveOldest,
|
haveOldest,
|
||||||
isNearBottom,
|
isNearBottom,
|
||||||
items,
|
items,
|
||||||
|
messageChangeCounter,
|
||||||
messageLoadingState,
|
messageLoadingState,
|
||||||
oldestUnseenIndex:
|
oldestUnseenIndex:
|
||||||
isNumber(oldestUnseenIndex) && oldestUnseenIndex >= 0
|
isNumber(oldestUnseenIndex) && oldestUnseenIndex >= 0
|
||||||
|
@ -899,6 +901,7 @@ export const getConversationMessagesSelector = createSelector(
|
||||||
return {
|
return {
|
||||||
haveNewest: false,
|
haveNewest: false,
|
||||||
haveOldest: false,
|
haveOldest: false,
|
||||||
|
messageChangeCounter: 0,
|
||||||
messageLoadingState: TimelineMessageLoadingState.DoingInitialLoad,
|
messageLoadingState: TimelineMessageLoadingState.DoingInitialLoad,
|
||||||
scrollToIndexCounter: 0,
|
scrollToIndexCounter: 0,
|
||||||
totalUnseen: 0,
|
totalUnseen: 0,
|
||||||
|
|
|
@ -55,21 +55,22 @@ const {
|
||||||
conversationStoppedByMissingVerification,
|
conversationStoppedByMissingVerification,
|
||||||
createGroup,
|
createGroup,
|
||||||
discardMessages,
|
discardMessages,
|
||||||
|
messageChanged,
|
||||||
openConversationInternal,
|
openConversationInternal,
|
||||||
repairNewestMessage,
|
repairNewestMessage,
|
||||||
repairOldestMessage,
|
repairOldestMessage,
|
||||||
|
resetAllChatColors,
|
||||||
|
reviewGroupMemberNameCollision,
|
||||||
|
reviewMessageRequestNameCollision,
|
||||||
setComposeGroupAvatar,
|
setComposeGroupAvatar,
|
||||||
setComposeGroupName,
|
setComposeGroupName,
|
||||||
setComposeSearchTerm,
|
setComposeSearchTerm,
|
||||||
setPreJoinConversation,
|
setPreJoinConversation,
|
||||||
showArchivedConversations,
|
showArchivedConversations,
|
||||||
|
showChooseGroupMembers,
|
||||||
showInbox,
|
showInbox,
|
||||||
startComposing,
|
startComposing,
|
||||||
showChooseGroupMembers,
|
|
||||||
startSettingGroupMetadata,
|
startSettingGroupMetadata,
|
||||||
resetAllChatColors,
|
|
||||||
reviewGroupMemberNameCollision,
|
|
||||||
reviewMessageRequestNameCollision,
|
|
||||||
toggleConversationInChooseMembers,
|
toggleConversationInChooseMembers,
|
||||||
} = actions;
|
} = actions;
|
||||||
|
|
||||||
|
@ -317,7 +318,7 @@ describe('both/state/ducks/conversations', () => {
|
||||||
function getDefaultMessage(id: string): MessageType {
|
function getDefaultMessage(id: string): MessageType {
|
||||||
return {
|
return {
|
||||||
attachments: [],
|
attachments: [],
|
||||||
conversationId: 'conversationId',
|
conversationId,
|
||||||
id,
|
id,
|
||||||
received_at: previousTime,
|
received_at: previousTime,
|
||||||
sent_at: previousTime,
|
sent_at: previousTime,
|
||||||
|
@ -331,6 +332,7 @@ describe('both/state/ducks/conversations', () => {
|
||||||
|
|
||||||
function getDefaultConversationMessage(): ConversationMessageType {
|
function getDefaultConversationMessage(): ConversationMessageType {
|
||||||
return {
|
return {
|
||||||
|
messageChangeCounter: 0,
|
||||||
messageIds: [],
|
messageIds: [],
|
||||||
metrics: {
|
metrics: {
|
||||||
totalUnseen: 0,
|
totalUnseen: 0,
|
||||||
|
@ -1317,6 +1319,7 @@ describe('both/state/ducks/conversations', () => {
|
||||||
},
|
},
|
||||||
messagesByConversation: {
|
messagesByConversation: {
|
||||||
[conversationId]: {
|
[conversationId]: {
|
||||||
|
messageChangeCounter: 0,
|
||||||
metrics: {
|
metrics: {
|
||||||
totalUnseen: 0,
|
totalUnseen: 0,
|
||||||
},
|
},
|
||||||
|
@ -1387,6 +1390,139 @@ describe('both/state/ducks/conversations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('MESSAGE_CHANGED', () => {
|
||||||
|
const startState: ConversationsStateType = {
|
||||||
|
...getEmptyState(),
|
||||||
|
conversationLookup: {
|
||||||
|
[conversationId]: {
|
||||||
|
...getDefaultConversation(),
|
||||||
|
id: conversationId,
|
||||||
|
groupVersion: 2,
|
||||||
|
groupId: 'dGhpc2lzYWdyb3VwaWR0aGlzaXNhZ3JvdXBpZHRoaXM=',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
messagesByConversation: {
|
||||||
|
[conversationId]: {
|
||||||
|
messageChangeCounter: 0,
|
||||||
|
messageIds: [messageId, messageIdTwo, messageIdThree],
|
||||||
|
metrics: {
|
||||||
|
totalUnseen: 0,
|
||||||
|
},
|
||||||
|
scrollToMessageCounter: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
messagesLookup: {
|
||||||
|
[messageId]: {
|
||||||
|
...getDefaultMessage(messageId),
|
||||||
|
displayLimit: undefined,
|
||||||
|
},
|
||||||
|
[messageIdTwo]: {
|
||||||
|
...getDefaultMessage(messageIdTwo),
|
||||||
|
displayLimit: undefined,
|
||||||
|
},
|
||||||
|
[messageIdThree]: {
|
||||||
|
...getDefaultMessage(messageIdThree),
|
||||||
|
displayLimit: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const changedMessage = {
|
||||||
|
...getDefaultMessage(messageId),
|
||||||
|
body: 'changed',
|
||||||
|
displayLimit: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('updates message data', () => {
|
||||||
|
const state = reducer(
|
||||||
|
startState,
|
||||||
|
messageChanged(messageId, conversationId, changedMessage)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(state.messagesLookup[messageId], changedMessage);
|
||||||
|
assert.strictEqual(
|
||||||
|
state.messagesByConversation[conversationId]?.messageChangeCounter,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not update lookup if it is a story reply', () => {
|
||||||
|
const state = reducer(
|
||||||
|
startState,
|
||||||
|
messageChanged(messageId, conversationId, {
|
||||||
|
...changedMessage,
|
||||||
|
storyId: 'story-id',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
state.messagesLookup[messageId],
|
||||||
|
startState.messagesLookup[messageId]
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
state.messagesByConversation[conversationId]?.messageChangeCounter,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments message change counter if new message has reactions', () => {
|
||||||
|
const changedMessageWithReaction: MessageType = {
|
||||||
|
...changedMessage,
|
||||||
|
reactions: [
|
||||||
|
{
|
||||||
|
emoji: '🎁',
|
||||||
|
fromId: 'some-other-id',
|
||||||
|
timestamp: 2222,
|
||||||
|
targetTimestamp: 1111,
|
||||||
|
targetAuthorUuid: 'author-uuid',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const state = reducer(
|
||||||
|
startState,
|
||||||
|
messageChanged(messageId, conversationId, changedMessageWithReaction)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
state.messagesLookup[messageId],
|
||||||
|
changedMessageWithReaction
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
state.messagesByConversation[conversationId]?.messageChangeCounter,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment message change counter if only old message had reactions', () => {
|
||||||
|
const updatedStartState = {
|
||||||
|
...startState,
|
||||||
|
messagesLookup: {
|
||||||
|
[messageId]: {
|
||||||
|
...startState.messagesLookup[messageId],
|
||||||
|
reactions: [
|
||||||
|
{
|
||||||
|
emoji: '🎁',
|
||||||
|
fromId: 'some-other-id',
|
||||||
|
timestamp: 2222,
|
||||||
|
targetTimestamp: 1111,
|
||||||
|
targetAuthorUuid: 'author-uuid',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const state = reducer(
|
||||||
|
updatedStartState,
|
||||||
|
messageChanged(messageId, conversationId, changedMessage)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(state.messagesLookup[messageId], changedMessage);
|
||||||
|
assert.strictEqual(
|
||||||
|
state.messagesByConversation[conversationId]?.messageChangeCounter,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('SHOW_ARCHIVED_CONVERSATIONS', () => {
|
describe('SHOW_ARCHIVED_CONVERSATIONS', () => {
|
||||||
it('is a no-op when already at the archive', () => {
|
it('is a no-op when already at the archive', () => {
|
||||||
const state = {
|
const state = {
|
||||||
|
|
Loading…
Reference in New Issue