MediaGallery: Only update if changed message is within time range

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
automated-signal 2024-09-27 14:39:42 -05:00 committed by GitHub
parent 60984376f4
commit 3e37fa1163
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,7 @@
// Copyright 2022 Signal Messenger, LLC // Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { orderBy } from 'lodash';
import type { ThunkAction } from 'redux-thunk'; import type { ThunkAction } from 'redux-thunk';
import type { ReadonlyDeep } from 'type-fest'; import type { ReadonlyDeep } from 'type-fest';
@ -35,6 +36,7 @@ import type { MessageAttributesType } from '../../model-types';
type MediaItemMessage = ReadonlyDeep<{ type MediaItemMessage = ReadonlyDeep<{
attachments: Array<AttachmentType>; attachments: Array<AttachmentType>;
// Note that this reflects the sender, and not the parent conversation
conversationId: string; conversationId: string;
id: string; id: string;
receivedAt: number; receivedAt: number;
@ -107,6 +109,19 @@ type MediaGalleryActionType = ReadonlyDeep<
| SetLoadingActionType | SetLoadingActionType
>; >;
function _sortMedia(media: ReadonlyArray<MediaType>): ReadonlyArray<MediaType> {
return orderBy(media, [
'message.receivedAt',
'message.sentAt',
'message.index',
]);
}
function _sortDocuments(
documents: ReadonlyArray<MediaItemType>
): ReadonlyArray<MediaItemType> {
return orderBy(documents, ['message.receivedAt', 'message.sentAt']);
}
function _getMediaItemMessage( function _getMediaItemMessage(
message: ReadonlyDeep<MessageAttributesType> message: ReadonlyDeep<MessageAttributesType>
): MediaItemMessage { ): MediaItemMessage {
@ -128,10 +143,10 @@ function _getMediaItemMessage(
function _cleanVisualAttachments( function _cleanVisualAttachments(
rawMedia: ReadonlyDeep<ReadonlyArray<MessageAttributesType>> rawMedia: ReadonlyDeep<ReadonlyArray<MessageAttributesType>>
): ReadonlyArray<MediaType> { ): ReadonlyArray<MediaType> {
let index = 0;
return rawMedia return rawMedia
.flatMap(message => { .flatMap(message => {
let index = 0;
return (message.attachments || []).map( return (message.attachments || []).map(
(attachment: AttachmentType): MediaType | undefined => { (attachment: AttachmentType): MediaType | undefined => {
if ( if (
@ -276,8 +291,8 @@ function loadMoreMedia(
return; return;
} }
const firstMedia = previousMedia[0]; const oldestLoadedMedia = previousMedia[0];
if (!firstMedia) { if (!oldestLoadedMedia) {
log.warn('loadMoreMedia: no previous media; calling initialLoad()'); log.warn('loadMoreMedia: no previous media; calling initialLoad()');
initialLoad(conversationId)(dispatch, getState, {}); initialLoad(conversationId)(dispatch, getState, {});
return; return;
@ -288,7 +303,7 @@ function loadMoreMedia(
payload: { loading: true }, payload: { loading: true },
}); });
const { sentAt, receivedAt, id: messageId } = firstMedia.message; const { sentAt, receivedAt, id: messageId } = oldestLoadedMedia.message;
const rawMedia = await DataReader.getOlderMessagesByConversation({ const rawMedia = await DataReader.getOlderMessagesByConversation({
conversationId, conversationId,
@ -336,8 +351,8 @@ function loadMoreDocuments(
return; return;
} }
const firstDocument = previousDocuments[0]; const oldestLoadedDocument = previousDocuments[0];
if (!firstDocument) { if (!oldestLoadedDocument) {
log.warn( log.warn(
'loadMoreDocuments: no previous documents; calling initialLoad()' 'loadMoreDocuments: no previous documents; calling initialLoad()'
); );
@ -350,7 +365,7 @@ function loadMoreDocuments(
payload: { loading: true }, payload: { loading: true },
}); });
const { sentAt, receivedAt, id: messageId } = firstDocument.message; const { sentAt, receivedAt, id: messageId } = oldestLoadedDocument.message;
const rawDocuments = await DataReader.getOlderMessagesByConversation({ const rawDocuments = await DataReader.getOlderMessagesByConversation({
conversationId, conversationId,
@ -415,7 +430,9 @@ export function reducer(
return { return {
...state, ...state,
loading: false, loading: false,
...payload, conversationId: payload.conversationId,
media: _sortMedia(payload.media),
documents: _sortDocuments(payload.documents),
haveOldestMedia: payload.media.length === 0, haveOldestMedia: payload.media.length === 0,
haveOldestDocument: payload.documents.length === 0, haveOldestDocument: payload.documents.length === 0,
}; };
@ -431,7 +448,7 @@ export function reducer(
...state, ...state,
loading: false, loading: false,
haveOldestMedia: media.length === 0, haveOldestMedia: media.length === 0,
media: media.concat(state.media), media: _sortMedia(media.concat(state.media)),
}; };
} }
@ -445,12 +462,13 @@ export function reducer(
...state, ...state,
loading: false, loading: false,
haveOldestDocument: documents.length === 0, haveOldestDocument: documents.length === 0,
documents: documents.concat(state.documents), documents: _sortDocuments(documents.concat(state.documents)),
}; };
} }
// We don't capture the initial message add, but we do capture the moment when its // A time-ordered subset of all conversation media is loaded at once.
// attachments have been downloaded // When a message changes, check that the changed message falls within this time range,
// and if so insert it into the loaded media.
if (action.type === MESSAGE_CHANGED) { if (action.type === MESSAGE_CHANGED) {
const { payload } = action; const { payload } = action;
const { conversationId, data: message } = payload; const { conversationId, data: message } = payload;
@ -459,37 +477,66 @@ export function reducer(
return state; return state;
} }
if (!message.attachments || message.attachments.length === 0) {
return state;
}
const mediaWithout = state.media.filter( const mediaWithout = state.media.filter(
item => item.message.id !== message.id item => item.message.id !== message.id
); );
const documentsWithout = state.documents.filter( const documentsWithout = state.documents.filter(
item => item.message.id !== message.id item => item.message.id !== message.id
); );
const mediaDifference = state.media.length - mediaWithout.length;
const documentDifference = state.documents.length - documentsWithout.length;
if (message.deletedForEveryone) { if (message.deletedForEveryone || message.isErased) {
return { if (mediaDifference > 0 || documentDifference > 0) {
...state, return {
media: mediaWithout, ...state,
documents: documentsWithout, media: mediaWithout,
}; documents: documentsWithout,
};
}
return state;
} }
// Check whether we have new downloaded media, or an attachment has been deleted const oldestLoadedMedia = state.media[0];
const mediaCount = state.media.length - mediaWithout.length; const oldestLoadedDocument = state.documents[0];
const documentCount = state.documents.length - mediaWithout.length;
const media = _cleanVisualAttachments([message]); const newMedia = _cleanVisualAttachments([message]);
const documents = _cleanFileAttachments([message]); const newDocuments = _cleanFileAttachments([message]);
if (mediaCount !== media.length || documentCount !== documents.length) { let { documents, haveOldestDocument, haveOldestMedia, media } = state;
const inMediaTimeRange =
!oldestLoadedMedia ||
(message.received_at >= oldestLoadedMedia.message.receivedAt &&
message.sent_at >= oldestLoadedMedia.message.sentAt);
if (mediaDifference !== media.length && inMediaTimeRange) {
media = _sortMedia(mediaWithout.concat(newMedia));
} else if (!inMediaTimeRange) {
haveOldestMedia = false;
}
const inDocumentTimeRange =
!oldestLoadedDocument ||
(message.received_at >= oldestLoadedDocument.message.receivedAt &&
message.sent_at >= oldestLoadedDocument.message.sentAt);
if (documentDifference !== documents.length && inDocumentTimeRange) {
documents = _sortDocuments(documentsWithout.concat(newDocuments));
} else if (!inDocumentTimeRange) {
haveOldestDocument = false;
}
if (
state.haveOldestDocument !== haveOldestDocument ||
state.haveOldestMedia !== haveOldestMedia ||
state.documents !== documents ||
state.media !== media
) {
return { return {
...state, ...state,
media: mediaWithout.concat(media), documents,
documents: documentsWithout.concat(documents), haveOldestDocument,
haveOldestMedia,
media,
}; };
} }