Message: Fix downloading scheduled media (#3174)

This commit is contained in:
Alexander Zinchuk 2023-05-15 10:57:33 +02:00
parent a71a3bc53b
commit 96670d0454
19 changed files with 126 additions and 76 deletions

View File

@ -185,6 +185,7 @@ export function buildApiMessageWithChatId(
if (action) { if (action) {
content.action = action; content.action = action;
} }
const isScheduled = mtpMessage.date > (Math.round(Date.now() / 1000) + getServerTimeOffset());
const isInvoiceMedia = mtpMessage.media instanceof GramJs.MessageMediaInvoice const isInvoiceMedia = mtpMessage.media instanceof GramJs.MessageMediaInvoice
&& Boolean(mtpMessage.media.extendedMedia); && Boolean(mtpMessage.media.extendedMedia);
@ -216,6 +217,7 @@ export function buildApiMessageWithChatId(
senderId: fromId || (mtpMessage.out && mtpMessage.post && currentUserId) || chatId, senderId: fromId || (mtpMessage.out && mtpMessage.post && currentUserId) || chatId,
views: mtpMessage.views, views: mtpMessage.views,
forwards: mtpMessage.forwards, forwards: mtpMessage.forwards,
isScheduled,
isFromScheduled: mtpMessage.fromScheduled, isFromScheduled: mtpMessage.fromScheduled,
isSilent: mtpMessage.silent, isSilent: mtpMessage.silent,
isPinned: mtpMessage.pinned, isPinned: mtpMessage.pinned,

View File

@ -53,7 +53,7 @@ type OwnProps = {
className?: string; className?: string;
isSelectable?: boolean; isSelectable?: boolean;
isSelected?: boolean; isSelected?: boolean;
isDownloading: boolean; isDownloading?: boolean;
isTranscribing?: boolean; isTranscribing?: boolean;
isTranscribed?: boolean; isTranscribed?: boolean;
canDownload?: boolean; canDownload?: boolean;

View File

@ -39,7 +39,7 @@ type OwnProps = {
className?: string; className?: string;
sender?: string; sender?: string;
autoLoadFileMaxSizeMb?: number; autoLoadFileMaxSizeMb?: number;
isDownloading: boolean; isDownloading?: boolean;
onCancelUpload?: () => void; onCancelUpload?: () => void;
onMediaClick?: () => void; onMediaClick?: () => void;
onDateClick?: (messageId: number, chatId: string) => void; onDateClick?: (messageId: number, chatId: string) => void;

View File

@ -103,7 +103,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
onPlay={handlePlayAudio} onPlay={handlePlayAudio}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}
canDownload={!chatsById[message.chatId]?.isProtected && !message.isProtected} canDownload={!chatsById[message.chatId]?.isProtected && !message.isProtected}
isDownloading={activeDownloads[message.chatId]?.includes(message.id)} isDownloading={activeDownloads[message.chatId]?.ids?.includes(message.id)}
/> />
</div> </div>
); );

View File

@ -105,7 +105,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
smaller smaller
sender={getSenderName(lang, message, chatsById, usersById)} sender={getSenderName(lang, message, chatsById, usersById)}
className="scroll-item" className="scroll-item"
isDownloading={activeDownloads[message.chatId]?.includes(message.id)} isDownloading={activeDownloads[message.chatId]?.ids?.includes(message.id)}
observeIntersection={observeIntersectionForMedia} observeIntersection={observeIntersectionForMedia}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}
/> />

View File

@ -1,4 +1,4 @@
import type { GlobalState } from '../../../../global/types'; import type { GlobalState, TabState } from '../../../../global/types';
import type { import type {
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiUser, ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiUser,
} from '../../../../api/types'; } from '../../../../api/types';
@ -15,7 +15,7 @@ export type StateProps = {
foundIds?: string[]; foundIds?: string[];
lastSyncTime?: number; lastSyncTime?: number;
searchChatId?: string; searchChatId?: string;
activeDownloads: Record<string, number[]>; activeDownloads: TabState['activeDownloads']['byChatId'];
isChatProtected?: boolean; isChatProtected?: boolean;
}; };

View File

@ -1,6 +1,6 @@
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import { memo, useCallback, useEffect } from '../../lib/teact/teact'; import { memo, useCallback, useEffect } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
import type { GlobalState, TabState } from '../../global/types'; import type { GlobalState, TabState } from '../../global/types';
import type { ApiMessage } from '../../api/types'; import type { ApiMessage } from '../../api/types';
@ -13,6 +13,7 @@ import download from '../../util/download';
import { import {
getMessageContentFilename, getMessageMediaFormat, getMessageMediaHash, getMessageContentFilename, getMessageMediaFormat, getMessageMediaHash,
} from '../../global/helpers'; } from '../../global/helpers';
import { compact } from '../../util/iteratees';
import useRunDebounced from '../../hooks/useRunDebounced'; import useRunDebounced from '../../hooks/useRunDebounced';
@ -28,7 +29,6 @@ const downloadedMessages = new Set<ApiMessage>();
const DownloadManager: FC<StateProps> = ({ const DownloadManager: FC<StateProps> = ({
activeDownloads, activeDownloads,
messages,
}) => { }) => {
const { cancelMessagesMediaDownload, showNotification } = getActions(); const { cancelMessagesMediaDownload, showNotification } = getActions();
@ -45,9 +45,16 @@ const DownloadManager: FC<StateProps> = ({
}, [cancelMessagesMediaDownload, runDebounced]); }, [cancelMessagesMediaDownload, runDebounced]);
useEffect(() => { useEffect(() => {
const activeMessages = Object.entries(activeDownloads).map(([chatId, messageIds]) => ( // No need for expensive global updates on messages, so we avoid them
messageIds.map((id) => messages![chatId].byId[id]) const messages = getGlobal().messages.byChatId;
)).flat(); const scheduledMessages = getGlobal().scheduledMessages.byChatId;
const activeMessages = Object.entries(activeDownloads).map(([chatId, chatActiveDownloads]) => {
const chatMessages = chatActiveDownloads.ids?.map((id) => messages[chatId]?.byId[id]);
const chatScheduledMessages = chatActiveDownloads.scheduledIds?.map((id) => scheduledMessages[chatId]?.byId[id]);
return compact([...chatMessages || [], ...chatScheduledMessages || []]);
}).flat();
if (!activeMessages.length) { if (!activeMessages.length) {
processedMessages.clear(); processedMessages.clear();
@ -104,7 +111,7 @@ const DownloadManager: FC<StateProps> = ({
handleMessageDownloaded(message); handleMessageDownloaded(message);
}); });
}); });
}, [messages, activeDownloads, cancelMessagesMediaDownload, handleMessageDownloaded, showNotification]); }, [activeDownloads, cancelMessagesMediaDownload, handleMessageDownloaded, showNotification]);
return undefined; return undefined;
}; };
@ -112,11 +119,9 @@ const DownloadManager: FC<StateProps> = ({
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => { (global): StateProps => {
const activeDownloads = selectTabState(global).activeDownloads.byChatId; const activeDownloads = selectTabState(global).activeDownloads.byChatId;
const hasActiveDownloads = Object.values(activeDownloads).some((messageIds) => messageIds.length);
return { return {
activeDownloads, activeDownloads,
messages: hasActiveDownloads ? global.messages.byChatId : undefined,
}; };
}, },
)(DownloadManager)); )(DownloadManager));

View File

@ -37,7 +37,7 @@ import DeleteProfilePhotoModal from '../common/DeleteProfilePhotoModal';
import './MediaViewerActions.scss'; import './MediaViewerActions.scss';
type StateProps = { type StateProps = {
isDownloading: boolean; isDownloading?: boolean;
isProtected?: boolean; isProtected?: boolean;
isChatProtected?: boolean; isChatProtected?: boolean;
canDelete?: boolean; canDelete?: boolean;

View File

@ -12,7 +12,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
import withSelectControl from './hocs/withSelectControl'; import withSelectControl from './hocs/withSelectControl';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { import {
selectActiveDownloadIds, selectActiveDownloads,
selectCanAutoLoadMedia, selectCanAutoLoadMedia,
selectCanAutoPlayMedia, selectCanAutoPlayMedia,
selectTheme, selectTheme,
@ -40,7 +40,7 @@ type OwnProps = {
type StateProps = { type StateProps = {
theme: ISettings['theme']; theme: ISettings['theme'];
uploadsById: GlobalState['fileUploads']['byMessageLocalId']; uploadsById: GlobalState['fileUploads']['byMessageLocalId'];
activeDownloadIds: number[]; activeDownloadIds?: number[];
}; };
const Album: FC<OwnProps & StateProps> = ({ const Album: FC<OwnProps & StateProps> = ({
@ -92,7 +92,7 @@ const Album: FC<OwnProps & StateProps> = ({
isProtected={isProtected} isProtected={isProtected}
onClick={onMediaClick} onClick={onMediaClick}
onCancelUpload={handleCancelUpload} onCancelUpload={handleCancelUpload}
isDownloading={activeDownloadIds.includes(message.id)} isDownloading={activeDownloadIds?.includes(message.id)}
theme={theme} theme={theme}
/> />
); );
@ -110,7 +110,7 @@ const Album: FC<OwnProps & StateProps> = ({
isProtected={isProtected} isProtected={isProtected}
onClick={onMediaClick} onClick={onMediaClick}
onCancelUpload={handleCancelUpload} onCancelUpload={handleCancelUpload}
isDownloading={activeDownloadIds.includes(message.id)} isDownloading={activeDownloadIds?.includes(message.id)}
theme={theme} theme={theme}
/> />
); );
@ -135,11 +135,13 @@ export default withGlobal<OwnProps>(
(global, { album }): StateProps => { (global, { album }): StateProps => {
const { chatId } = album.mainMessage; const { chatId } = album.mainMessage;
const theme = selectTheme(global); const theme = selectTheme(global);
const activeDownloadIds = selectActiveDownloadIds(global, chatId); const activeDownloads = selectActiveDownloads(global, chatId);
const isScheduled = album.mainMessage.isScheduled;
return { return {
theme, theme,
uploadsById: global.fileUploads.byMessageLocalId, uploadsById: global.fileUploads.byMessageLocalId,
activeDownloadIds, activeDownloadIds: isScheduled ? activeDownloads?.scheduledIds : activeDownloads?.ids,
}; };
}, },
)(Album); )(Album);

View File

@ -4,14 +4,14 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import type { MessageListType } from '../../../global/types'; import type { MessageListType, TabState } from '../../../global/types';
import type { import type {
ApiAvailableReaction, ApiStickerSetInfo, ApiMessage, ApiStickerSet, ApiChatReactions, ApiReaction, ApiThreadInfo, ApiAvailableReaction, ApiStickerSetInfo, ApiMessage, ApiStickerSet, ApiChatReactions, ApiReaction, ApiThreadInfo,
} from '../../../api/types'; } from '../../../api/types';
import type { IAlbum, IAnchorPosition } from '../../../types'; import type { IAlbum, IAnchorPosition } from '../../../types';
import { import {
selectActiveDownloadIds, selectActiveDownloads,
selectAllowedMessageActions, selectAllowedMessageActions,
selectCanPlayAnimatedEmojis, selectCanPlayAnimatedEmojis,
selectCanScheduleUntilOnline, selectCanScheduleUntilOnline,
@ -101,7 +101,7 @@ type StateProps = {
canSaveGif?: boolean; canSaveGif?: boolean;
canRevote?: boolean; canRevote?: boolean;
canClosePoll?: boolean; canClosePoll?: boolean;
activeDownloads: number[]; activeDownloads?: TabState['activeDownloads']['byChatId'][number];
canShowSeenBy?: boolean; canShowSeenBy?: boolean;
enabledReactions?: ApiChatReactions; enabledReactions?: ApiChatReactions;
canScheduleUntilOnline?: boolean; canScheduleUntilOnline?: boolean;
@ -249,8 +249,15 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
return Object.keys(message.seenByDates).slice(0, 3).map((id) => usersById[id]).filter(Boolean); return Object.keys(message.seenByDates).slice(0, 3).map((id) => usersById[id]).filter(Boolean);
}, [message.reactions?.recentReactions, message.seenByDates]); }, [message.reactions?.recentReactions, message.seenByDates]);
const isDownloading = album ? album.messages.some((msg) => activeDownloads.includes(msg.id)) const isDownloading = useMemo(() => {
: activeDownloads.includes(message.id); if (album) {
return album.messages.some((msg) => {
return activeDownloads?.[message.isScheduled ? 'scheduledIds' : 'ids']?.includes(msg.id);
});
}
return activeDownloads?.[message.isScheduled ? 'scheduledIds' : 'ids']?.includes(message.id);
}, [activeDownloads, album, message]);
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
setIsMenuOpen(false); setIsMenuOpen(false);
@ -566,7 +573,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { message, messageListType, detectedLanguage }): StateProps => { (global, { message, messageListType, detectedLanguage }): StateProps => {
const { threadId } = selectCurrentMessageList(global) || {}; const { threadId } = selectCurrentMessageList(global) || {};
const activeDownloads = selectActiveDownloadIds(global, message.chatId); const activeDownloads = selectActiveDownloads(global, message.chatId);
const chat = selectChat(global, message.chatId); const chat = selectChat(global, message.chatId);
const { seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions } = global.appConfig || {}; const { seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions } = global.appConfig || {};
const { const {

View File

@ -228,7 +228,7 @@ type StateProps = {
isInSelectMode?: boolean; isInSelectMode?: boolean;
isSelected?: boolean; isSelected?: boolean;
isGroupSelected?: boolean; isGroupSelected?: boolean;
isDownloading: boolean; isDownloading?: boolean;
threadId?: number; threadId?: number;
isPinnedList?: boolean; isPinnedList?: boolean;
isPinned?: boolean; isPinned?: boolean;

View File

@ -51,7 +51,7 @@ export type OwnProps = {
dimensions?: IMediaDimensions & { isSmall?: boolean }; dimensions?: IMediaDimensions & { isSmall?: boolean };
asForwarded?: boolean; asForwarded?: boolean;
nonInteractive?: boolean; nonInteractive?: boolean;
isDownloading: boolean; isDownloading?: boolean;
isProtected?: boolean; isProtected?: boolean;
theme: ISettings['theme']; theme: ISettings['theme'];
onClick?: (id: number) => void; onClick?: (id: number) => void;

View File

@ -47,7 +47,7 @@ export type OwnProps = {
dimensions?: IMediaDimensions; dimensions?: IMediaDimensions;
asForwarded?: boolean; asForwarded?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
isDownloading: boolean; isDownloading?: boolean;
isProtected?: boolean; isProtected?: boolean;
onClick?: (id: number) => void; onClick?: (id: number) => void;
onCancelUpload?: (message: ApiMessage) => void; onCancelUpload?: (message: ApiMessage) => void;

View File

@ -28,7 +28,7 @@ import {
getHasAdminRight, isChatAdmin, isChatChannel, isChatGroup, isUserBot, isUserId, isUserRightBanned, getHasAdminRight, isChatAdmin, isChatChannel, isChatGroup, isUserBot, isUserId, isUserRightBanned,
} from '../../global/helpers'; } from '../../global/helpers';
import { import {
selectActiveDownloadIds, selectActiveDownloads,
selectChat, selectChat,
selectChatFullInfo, selectChatFullInfo,
selectChatMessages, selectChatMessages,
@ -97,7 +97,7 @@ type StateProps = {
isRightColumnShown: boolean; isRightColumnShown: boolean;
isRestricted?: boolean; isRestricted?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
activeDownloadIds: number[]; activeDownloadIds?: number[];
isChatProtected?: boolean; isChatProtected?: boolean;
}; };
@ -373,7 +373,7 @@ const Profile: FC<OwnProps & StateProps> = ({
withDate withDate
smaller smaller
className="scroll-item" className="scroll-item"
isDownloading={activeDownloadIds.includes(id)} isDownloading={activeDownloadIds?.includes(id)}
observeIntersection={observeIntersectionForMedia} observeIntersection={observeIntersectionForMedia}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}
/> />
@ -401,7 +401,7 @@ const Profile: FC<OwnProps & StateProps> = ({
onPlay={handlePlayAudio} onPlay={handlePlayAudio}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}
canDownload={!isChatProtected && !messagesById[id].isProtected} canDownload={!isChatProtected && !messagesById[id].isProtected}
isDownloading={activeDownloadIds.includes(id)} isDownloading={activeDownloadIds?.includes(id)}
/> />
)) ))
) : resultType === 'voice' ? ( ) : resultType === 'voice' ? (
@ -418,7 +418,7 @@ const Profile: FC<OwnProps & StateProps> = ({
onPlay={handlePlayAudio} onPlay={handlePlayAudio}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}
canDownload={!isChatProtected && !messagesById[id].isProtected} canDownload={!isChatProtected && !messagesById[id].isProtected}
isDownloading={activeDownloadIds.includes(id)} isDownloading={activeDownloadIds?.includes(id)}
/> />
)) ))
) : resultType === 'members' ? ( ) : resultType === 'members' ? (
@ -537,7 +537,7 @@ export default memo(withGlobal<OwnProps>(
const canAddMembers = hasMembersTab && chat const canAddMembers = hasMembersTab && chat
&& (getHasAdminRight(chat, 'inviteUsers') || !isUserRightBanned(chat, 'inviteUsers') || chat.isCreator); && (getHasAdminRight(chat, 'inviteUsers') || !isUserRightBanned(chat, 'inviteUsers') || chat.isCreator);
const canDeleteMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'banUsers') || chat.isCreator); const canDeleteMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'banUsers') || chat.isCreator);
const activeDownloadIds = selectActiveDownloadIds(global, chatId); const activeDownloads = selectActiveDownloads(global, chatId);
let hasCommonChatsTab; let hasCommonChatsTab;
let resolvedUserId; let resolvedUserId;
@ -564,7 +564,7 @@ export default memo(withGlobal<OwnProps>(
isRightColumnShown: selectIsRightColumnShown(global, isMobile), isRightColumnShown: selectIsRightColumnShown(global, isMobile),
isRestricted: chat?.isRestricted, isRestricted: chat?.isRestricted,
lastSyncTime: global.lastSyncTime, lastSyncTime: global.lastSyncTime,
activeDownloadIds, activeDownloadIds: activeDownloads?.ids,
usersById, usersById,
userStatusesById, userStatusesById,
chatsById, chatsById,

View File

@ -4,7 +4,7 @@ import type { ApiMessage } from '../../../api/types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { FocusDirection } from '../../../types'; import { FocusDirection } from '../../../types';
import type { import type {
TabState, GlobalState, ActionReturnType, GlobalState, ActionReturnType,
} from '../../types'; } from '../../types';
import { import {
@ -23,6 +23,8 @@ import {
replaceTabThreadParam, replaceTabThreadParam,
updateFocusDirection, updateFocusDirection,
updateFocusedMessage, updateFocusedMessage,
cancelMessageMediaDownload,
addActiveMessageMediaDownload,
} from '../../reducers'; } from '../../reducers';
import { import {
selectCurrentChat, selectCurrentChat,
@ -557,49 +559,23 @@ addActionHandler('openForwardMenuForSelectedMessages', (global, actions, payload
addActionHandler('cancelMessageMediaDownload', (global, actions, payload): ActionReturnType => { addActionHandler('cancelMessageMediaDownload', (global, actions, payload): ActionReturnType => {
const { message, tabId = getCurrentTabId() } = payload; const { message, tabId = getCurrentTabId() } = payload;
const tabState = selectTabState(global, tabId); return cancelMessageMediaDownload(global, message, tabId);
const byChatId = tabState.activeDownloads.byChatId[message.chatId];
if (!byChatId || !byChatId.length) return;
global = updateTabState(global, {
activeDownloads: {
byChatId: {
...tabState.activeDownloads.byChatId,
[message.chatId]: byChatId.filter((id) => id !== message.id),
},
},
}, tabId);
setGlobal(global);
}); });
addActionHandler('cancelMessagesMediaDownload', (global, actions, payload): ActionReturnType => { addActionHandler('cancelMessagesMediaDownload', (global, actions, payload): ActionReturnType => {
const { messages, tabId = getCurrentTabId() } = payload; const { messages, tabId = getCurrentTabId() } = payload;
const byChatId = selectTabState(global, tabId).activeDownloads.byChatId; for (const message of messages) {
const newByChatId: TabState['activeDownloads']['byChatId'] = {}; global = cancelMessageMediaDownload(global, message, tabId);
Object.keys(byChatId).forEach((chatId) => { }
newByChatId[chatId] = byChatId[chatId].filter((id) => !messages.find((message) => message.id === id));
}); return global;
return updateTabState(global, {
activeDownloads: {
byChatId: newByChatId,
},
}, tabId);
}); });
addActionHandler('downloadMessageMedia', (global, actions, payload): ActionReturnType => { addActionHandler('downloadMessageMedia', (global, actions, payload): ActionReturnType => {
const { message, tabId = getCurrentTabId() } = payload; const { message, tabId = getCurrentTabId() } = payload;
const tabState = selectTabState(global, tabId); return addActiveMessageMediaDownload(global, message, tabId);
global = updateTabState(global, {
activeDownloads: {
byChatId: {
...tabState.activeDownloads.byChatId,
[message.chatId]: [...(tabState.activeDownloads.byChatId[message.chatId] || []), message.id],
},
},
}, tabId);
setGlobal(global);
}); });
addActionHandler('downloadSelectedMessages', (global, actions, payload): ActionReturnType => { addActionHandler('downloadSelectedMessages', (global, actions, payload): ActionReturnType => {

View File

@ -230,7 +230,7 @@ export function getMessageContentFilename(message: ApiMessage) {
return content.audio.fileName; return content.audio.fileName;
} }
const baseFilename = getMessageKey(message); const baseFilename = `${getMessageKey(message)}${message.isScheduled ? '_scheduled' : ''}`;
if (photo) { if (photo) {
return `${baseFilename}.jpg`; return `${baseFilename}.jpg`;

View File

@ -691,3 +691,53 @@ export function updateTopicLastMessageId<T extends GlobalState>(
}, },
}; };
} }
export function addActiveMessageMediaDownload<T extends GlobalState>(
global: T,
message: ApiMessage,
...[tabId = getCurrentTabId()]: TabArgs<T>
) {
const tabState = selectTabState(global, tabId);
const byChatId = tabState.activeDownloads.byChatId[message.chatId] || {};
const currentIds = (message.isScheduled ? byChatId?.scheduledIds : byChatId?.ids) || [];
global = updateTabState(global, {
activeDownloads: {
byChatId: {
...tabState.activeDownloads.byChatId,
[message.chatId]: {
...byChatId,
[message.isScheduled ? 'scheduledIds' : 'ids']: unique([...currentIds, message.id]),
},
},
},
}, tabId);
return global;
}
export function cancelMessageMediaDownload<T extends GlobalState>(
global: T,
message: ApiMessage,
...[tabId = getCurrentTabId()]: TabArgs<T>
) {
const tabState = selectTabState(global, tabId);
const byChatId = tabState.activeDownloads.byChatId[message.chatId];
if (!byChatId) return global;
const currentIds = (message.isScheduled ? byChatId.scheduledIds : byChatId.ids) || [];
global = updateTabState(global, {
activeDownloads: {
byChatId: {
...tabState.activeDownloads.byChatId,
[message.chatId]: {
...byChatId,
[message.isScheduled ? 'scheduledIds' : 'ids']: currentIds.filter((id) => id !== message.id),
},
},
},
}, tabId);
return global;
}

View File

@ -725,14 +725,17 @@ export function selectIsDownloading<T extends GlobalState>(
...[tabId = getCurrentTabId()]: TabArgs<T> ...[tabId = getCurrentTabId()]: TabArgs<T>
) { ) {
const activeInChat = selectTabState(global, tabId).activeDownloads.byChatId[message.chatId]; const activeInChat = selectTabState(global, tabId).activeDownloads.byChatId[message.chatId];
return activeInChat ? activeInChat.includes(message.id) : false; if (!activeInChat) return false;
return Boolean(message.isScheduled
? activeInChat.scheduledIds?.includes(message.id) : activeInChat.ids?.includes(message.id));
} }
export function selectActiveDownloadIds<T extends GlobalState>( export function selectActiveDownloads<T extends GlobalState>(
global: T, chatId: string, global: T, chatId: string,
...[tabId = getCurrentTabId()]: TabArgs<T> ...[tabId = getCurrentTabId()]: TabArgs<T>
) { ) {
return selectTabState(global, tabId).activeDownloads.byChatId[chatId] || MEMO_EMPTY_ARRAY; return selectTabState(global, tabId).activeDownloads.byChatId[chatId];
} }
export function selectUploadProgress<T extends GlobalState>(global: T, message: ApiMessage) { export function selectUploadProgress<T extends GlobalState>(global: T, message: ApiMessage) {

View File

@ -440,7 +440,12 @@ export type TabState = {
openedCustomEmojiSetIds?: string[]; openedCustomEmojiSetIds?: string[];
activeDownloads: { activeDownloads: {
byChatId: Record<string, number[]>; byChatId: {
[chatId: string]: {
ids?: number[];
scheduledIds?: number[];
};
};
}; };
statistics: { statistics: {