2022-12-20 17:50:23 +00:00
|
|
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
|
|
import type { ThunkAction } from 'redux-thunk';
|
|
|
|
|
|
|
|
|
|
import type { AttachmentType } from '../../types/Attachment';
|
|
|
|
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
|
|
|
|
import type {
|
|
|
|
|
ConversationUnloadedActionType,
|
2023-01-05 22:16:56 +00:00
|
|
|
|
MessageChangedActionType,
|
2022-12-20 17:50:23 +00:00
|
|
|
|
MessageDeletedActionType,
|
|
|
|
|
MessageExpiredActionType,
|
|
|
|
|
} from './conversations';
|
|
|
|
|
import type { MIMEType } from '../../types/MIME';
|
|
|
|
|
import type { MediaItemType } from '../../types/MediaItem';
|
|
|
|
|
import type { StateType as RootStateType } from '../reducer';
|
|
|
|
|
|
|
|
|
|
import dataInterface from '../../sql/Client';
|
|
|
|
|
import {
|
|
|
|
|
CONVERSATION_UNLOADED,
|
2023-01-05 22:16:56 +00:00
|
|
|
|
MESSAGE_CHANGED,
|
2022-12-20 17:50:23 +00:00
|
|
|
|
MESSAGE_DELETED,
|
|
|
|
|
MESSAGE_EXPIRED,
|
|
|
|
|
} from './conversations';
|
|
|
|
|
import { VERSION_NEEDED_FOR_DISPLAY } from '../../types/Message2';
|
|
|
|
|
import { isDownloading, hasFailed } from '../../types/Attachment';
|
|
|
|
|
import { isNotNil } from '../../util/isNotNil';
|
|
|
|
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
|
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
2022-12-20 17:50:23 +00:00
|
|
|
|
type MediaType = {
|
|
|
|
|
path: string;
|
|
|
|
|
objectURL: string;
|
|
|
|
|
thumbnailObjectUrl?: string;
|
|
|
|
|
contentType: MIMEType;
|
|
|
|
|
index: number;
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
message: {
|
|
|
|
|
attachments: Array<AttachmentType>;
|
|
|
|
|
conversationId: string;
|
|
|
|
|
id: string;
|
|
|
|
|
received_at: number;
|
|
|
|
|
received_at_ms: number;
|
|
|
|
|
sent_at: number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
2022-12-20 17:50:23 +00:00
|
|
|
|
export type MediaGalleryStateType = {
|
|
|
|
|
documents: Array<MediaItemType>;
|
|
|
|
|
media: Array<MediaType>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const LOAD_MEDIA_ITEMS = 'mediaGallery/LOAD_MEDIA_ITEMS';
|
|
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
2022-12-20 17:50:23 +00:00
|
|
|
|
type LoadMediaItemslActionType = {
|
|
|
|
|
type: typeof LOAD_MEDIA_ITEMS;
|
|
|
|
|
payload: {
|
|
|
|
|
documents: Array<MediaItemType>;
|
|
|
|
|
media: Array<MediaType>;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
2022-12-20 17:50:23 +00:00
|
|
|
|
type MediaGalleryActionType =
|
|
|
|
|
| ConversationUnloadedActionType
|
|
|
|
|
| LoadMediaItemslActionType
|
2023-01-05 22:16:56 +00:00
|
|
|
|
| MessageChangedActionType
|
2022-12-20 17:50:23 +00:00
|
|
|
|
| MessageDeletedActionType
|
|
|
|
|
| MessageExpiredActionType;
|
|
|
|
|
|
|
|
|
|
function loadMediaItems(
|
|
|
|
|
conversationId: string
|
|
|
|
|
): ThunkAction<void, RootStateType, unknown, LoadMediaItemslActionType> {
|
|
|
|
|
return async dispatch => {
|
|
|
|
|
const { getAbsoluteAttachmentPath, upgradeMessageSchema } =
|
|
|
|
|
window.Signal.Migrations;
|
|
|
|
|
|
|
|
|
|
// We fetch more documents than media as they don’t require to be loaded
|
|
|
|
|
// into memory right away. Revisit this once we have infinite scrolling:
|
|
|
|
|
const DEFAULT_MEDIA_FETCH_COUNT = 50;
|
|
|
|
|
const DEFAULT_DOCUMENTS_FETCH_COUNT = 150;
|
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
|
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
2022-12-20 17:50:23 +00:00
|
|
|
|
|
|
|
|
|
const rawMedia = await dataInterface.getMessagesWithVisualMediaAttachments(
|
|
|
|
|
conversationId,
|
|
|
|
|
{
|
|
|
|
|
limit: DEFAULT_MEDIA_FETCH_COUNT,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
const rawDocuments = await dataInterface.getMessagesWithFileAttachments(
|
|
|
|
|
conversationId,
|
|
|
|
|
{
|
|
|
|
|
limit: DEFAULT_DOCUMENTS_FETCH_COUNT,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// First we upgrade these messages to ensure that they have thumbnails
|
|
|
|
|
await Promise.all(
|
|
|
|
|
rawMedia.map(async message => {
|
|
|
|
|
const { schemaVersion } = message;
|
|
|
|
|
const model = window.MessageController.register(message.id, message);
|
|
|
|
|
|
|
|
|
|
if (schemaVersion && schemaVersion < VERSION_NEEDED_FOR_DISPLAY) {
|
|
|
|
|
const upgradedMsgAttributes = await upgradeMessageSchema(message);
|
|
|
|
|
model.set(upgradedMsgAttributes);
|
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
|
await dataInterface.saveMessage(upgradedMsgAttributes, { ourAci });
|
2022-12-20 17:50:23 +00:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const media: Array<MediaType> = rawMedia
|
|
|
|
|
.flatMap(message => {
|
|
|
|
|
return (message.attachments || []).map(
|
|
|
|
|
(
|
|
|
|
|
attachment: AttachmentType,
|
|
|
|
|
index: number
|
|
|
|
|
): MediaType | undefined => {
|
|
|
|
|
if (
|
|
|
|
|
!attachment.path ||
|
|
|
|
|
!attachment.thumbnail ||
|
|
|
|
|
isDownloading(attachment) ||
|
|
|
|
|
hasFailed(attachment)
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { thumbnail } = attachment;
|
|
|
|
|
return {
|
|
|
|
|
path: attachment.path,
|
|
|
|
|
objectURL: getAbsoluteAttachmentPath(attachment.path),
|
|
|
|
|
thumbnailObjectUrl: thumbnail?.path
|
|
|
|
|
? getAbsoluteAttachmentPath(thumbnail.path)
|
|
|
|
|
: undefined,
|
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index,
|
|
|
|
|
attachment,
|
|
|
|
|
message: {
|
|
|
|
|
attachments: message.attachments || [],
|
|
|
|
|
conversationId:
|
|
|
|
|
window.ConversationController.lookupOrCreate({
|
2023-08-16 20:54:39 +00:00
|
|
|
|
serviceId: message.sourceServiceId,
|
2022-12-20 17:50:23 +00:00
|
|
|
|
e164: message.source,
|
|
|
|
|
reason: 'conversation_view.showAllMedia',
|
|
|
|
|
})?.id || message.conversationId,
|
|
|
|
|
id: message.id,
|
|
|
|
|
received_at: message.received_at,
|
|
|
|
|
received_at_ms: Number(message.received_at_ms),
|
|
|
|
|
sent_at: message.sent_at,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
.filter(isNotNil);
|
|
|
|
|
|
|
|
|
|
// Unlike visual media, only one non-image attachment is supported
|
|
|
|
|
const documents: Array<MediaItemType> = rawDocuments
|
|
|
|
|
.map(message => {
|
|
|
|
|
const attachments = message.attachments || [];
|
|
|
|
|
const attachment = attachments[0];
|
|
|
|
|
if (!attachment) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index: 0,
|
|
|
|
|
attachment,
|
|
|
|
|
message: {
|
|
|
|
|
...message,
|
|
|
|
|
attachments: [attachment],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter(isNotNil);
|
|
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
|
type: LOAD_MEDIA_ITEMS,
|
|
|
|
|
payload: {
|
|
|
|
|
documents,
|
|
|
|
|
media,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const actions = {
|
|
|
|
|
loadMediaItems,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useMediaGalleryActions = (): BoundActionCreatorsMapObject<
|
|
|
|
|
typeof actions
|
|
|
|
|
> => useBoundActions(actions);
|
|
|
|
|
|
|
|
|
|
export function getEmptyState(): MediaGalleryStateType {
|
|
|
|
|
return {
|
|
|
|
|
documents: [],
|
|
|
|
|
media: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function reducer(
|
|
|
|
|
state: Readonly<MediaGalleryStateType> = getEmptyState(),
|
|
|
|
|
action: Readonly<MediaGalleryActionType>
|
|
|
|
|
): MediaGalleryStateType {
|
|
|
|
|
if (action.type === LOAD_MEDIA_ITEMS) {
|
|
|
|
|
return {
|
|
|
|
|
...state,
|
|
|
|
|
...action.payload,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-05 22:16:56 +00:00
|
|
|
|
if (action.type === MESSAGE_CHANGED) {
|
|
|
|
|
if (!action.payload.data.deletedForEveryone) {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...state,
|
|
|
|
|
media: state.media.filter(item => item.message.id !== action.payload.id),
|
|
|
|
|
documents: state.documents.filter(
|
|
|
|
|
item => item.message.id !== action.payload.id
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 17:50:23 +00:00
|
|
|
|
if (action.type === MESSAGE_DELETED || action.type === MESSAGE_EXPIRED) {
|
|
|
|
|
return {
|
|
|
|
|
...state,
|
|
|
|
|
media: state.media.filter(item => item.message.id !== action.payload.id),
|
|
|
|
|
documents: state.documents.filter(
|
|
|
|
|
item => item.message.id !== action.payload.id
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action.type === CONVERSATION_UNLOADED) {
|
|
|
|
|
return getEmptyState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return state;
|
|
|
|
|
}
|