From a953869683487c78f40eae84b87e1a907432dee5 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sun, 28 May 2023 14:32:18 +0200 Subject: [PATCH] Media: Allow sending messages while uploading (#3170) --- src/api/gramjs/apiBuilders/messages.ts | 17 ++++---------- src/api/gramjs/methods/messages.ts | 22 +++++++++---------- src/components/common/File.tsx | 14 ++++++++---- src/components/middle/MessageList.tsx | 4 ++-- src/components/middle/hooks/useScrollHooks.ts | 5 +++-- src/config.ts | 1 - src/global/helpers/messages.ts | 3 +-- src/global/selectors/messages.ts | 5 +++-- 8 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index c0af317c1..43e79a516 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -43,7 +43,6 @@ import { import { DELETED_COMMENTS_CHANNEL_ID, - LOCAL_MESSAGE_MIN_ID, SERVICE_NOTIFICATIONS_USER_ID, SPONSORED_MESSAGE_CACHE_MS, SUPPORTED_AUDIO_CONTENT_TYPES, @@ -65,22 +64,14 @@ import { buildApiCallDiscardReason } from './calls'; import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage'; import { getServerTimeOffset } from '../../../util/serverTime'; -const TIMESTAMP_BASE = 1676e9; // 2023-02-10 -const TIMESTAMP_PRECISION = 1e2; // 0.1s const LOCAL_MESSAGES_LIMIT = 1e6; // 1M const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp'; const INPUT_WAVEFORM_LENGTH = 63; -let localMessageCounter = LOCAL_MESSAGE_MIN_ID; - -// Local IDs need to be fractional to allow service notifications to be placed between real messages. -// It also allows to avoid collisions when sending messages from multiple tabs due to timestamp-based whole part. -// To support up to 1M local messages, the whole part must be below 8.5B (https://stackoverflow.com/a/57225494/903919). -// The overflow will happen when `datePart` is >3.59B which will be in June 2034. -function getNextLocalMessageId() { - const datePart = Math.round((Date.now() - TIMESTAMP_BASE) / TIMESTAMP_PRECISION); - return LOCAL_MESSAGE_MIN_ID + datePart + (++localMessageCounter / LOCAL_MESSAGES_LIMIT); +let localMessageCounter = 0; +function getNextLocalMessageId(lastMessageId = 0) { + return lastMessageId + (++localMessageCounter / LOCAL_MESSAGES_LIMIT); } let currentUserId!: string; @@ -1308,7 +1299,7 @@ export function buildLocalMessage( scheduledAt?: number, sendAs?: ApiChat | ApiUser, ): ApiMessage { - const localId = getNextLocalMessageId(); + const localId = getNextLocalMessageId(chat.lastMessage?.id); const media = attachment && buildUploadingMedia(attachment); const isChannel = chat.type === 'chatTypeChannel'; const isForum = chat.isForum; diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index c0a82cac6..4295b4d6d 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -214,7 +214,7 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message return { message, users }; } -let queue = Promise.resolve(); +let mediaQueue = Promise.resolve(); export function sendMessage( { @@ -236,6 +236,7 @@ export function sendMessage( shouldUpdateStickerSetOrder, }: { chat: ApiChat; + lastMessageId?: number; text?: string; entities?: ApiMessageEntity[]; replyingTo?: number; @@ -306,8 +307,7 @@ export function sendMessage( }, randomId, localMessage, onProgress); } - const prevQueue = queue; - queue = (async () => { + const sendPromise = (async () => { let media: GramJs.TypeInputMedia | undefined; if (attachment) { try { @@ -318,7 +318,7 @@ export function sendMessage( console.warn(err); } - await prevQueue; + await mediaQueue; return; } @@ -337,8 +337,6 @@ export function sendMessage( }); } - await prevQueue; - const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage; try { @@ -369,7 +367,7 @@ export function sendMessage( } })(); - return queue; + return sendPromise; } const groupedUploads: Record { + const prevMediaQueue = mediaQueue; + mediaQueue = (async () => { let media; try { media = await uploadMedia(localMessage, attachment, onProgress!); @@ -430,7 +428,7 @@ function sendGroupedMedia( groupedUploads[groupedId].counter--; - await prevQueue; + await prevMediaQueue; return; } @@ -440,7 +438,7 @@ function sendGroupedMedia( media as GramJs.InputMediaUploadedPhoto | GramJs.InputMediaUploadedDocument, ); - await prevQueue; + await prevMediaQueue; if (!inputMedia) { groupedUploads[groupedId].counter--; @@ -482,7 +480,7 @@ function sendGroupedMedia( if (update) handleMultipleLocalMessagesUpdate(localMessages, update); })(); - return queue; + return mediaQueue; } async function fetchInputMedia( diff --git a/src/components/common/File.tsx b/src/components/common/File.tsx index f0d755fe9..61c864a1b 100644 --- a/src/components/common/File.tsx +++ b/src/components/common/File.tsx @@ -1,6 +1,8 @@ -import type { RefObject } from 'react'; +import React, { + memo, useRef, useState, useMemo, +} from '../../lib/teact/teact'; + import type { FC } from '../../lib/teact/teact'; -import React, { memo, useRef, useState } from '../../lib/teact/teact'; import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment'; import buildClassName from '../../util/buildClassName'; @@ -20,7 +22,7 @@ import Link from '../ui/Link'; import './File.scss'; type OwnProps = { - ref?: RefObject; + ref?: React.RefObject; name: string; extension?: string; size: number; @@ -80,6 +82,10 @@ const File: FC = ({ const color = getColorFromExtension(extension); const sizeString = getFileSizeString(size); + const subtitle = useMemo(() => { + if (!isTransferring || !transferProgress) return sizeString; + return `${getFileSizeString(size * transferProgress)} / ${sizeString}`; + }, [isTransferring, size, sizeString, transferProgress]); const { width, height } = getDocumentThumbnailDimensions(smaller); @@ -146,7 +152,7 @@ const File: FC = ({
{renderText(name)}
- {isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString} + {subtitle} {sender && {renderText(sender)}} {!sender && Boolean(timestamp) && ( diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 98a7ed407..ee61e87c4 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -22,7 +22,6 @@ import { LoadMoreDirection } from '../../types'; import { ANIMATION_END_DELAY, - LOCAL_MESSAGE_MIN_ID, MESSAGE_LIST_SLICE, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; @@ -58,6 +57,7 @@ import { getDocumentMediaHash, getVideoDimensions, getPhotoFullDimensions, + isLocalMessageId, } from '../../global/helpers'; import { orderBy } from '../../util/iteratees'; import { DPR } from '../../util/windowEnvironment'; @@ -366,7 +366,7 @@ const MessageList: FC = ({ } // Loading history while sending a message can return the same message and cause ambiguity - const isLastMessageLocal = messageIds && messageIds[messageIds.length - 1] > LOCAL_MESSAGE_MIN_ID; + const isLastMessageLocal = messageIds && isLocalMessageId(messageIds[messageIds.length - 1]); if (isLastMessageLocal) { return; } diff --git a/src/components/middle/hooks/useScrollHooks.ts b/src/components/middle/hooks/useScrollHooks.ts index b6482f9d7..11e89c2dd 100644 --- a/src/components/middle/hooks/useScrollHooks.ts +++ b/src/components/middle/hooks/useScrollHooks.ts @@ -9,9 +9,10 @@ import { LoadMoreDirection } from '../../../types'; import type { MessageListType } from '../../../global/types'; import type { Signal } from '../../../util/signals'; -import { LOCAL_MESSAGE_MIN_ID } from '../../../config'; import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment'; import { debounce } from '../../../util/schedulers'; +import { isLocalMessageId } from '../../../global/helpers'; + import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver'; import useSyncEffect from '../../../hooks/useSyncEffect'; import { useStateRef } from '../../../hooks/useStateRef'; @@ -94,7 +95,7 @@ export default function useScrollHooks( } // Loading history while sending a message can return the same message and cause ambiguity - const isFirstMessageLocal = messageIds[0] > LOCAL_MESSAGE_MIN_ID; + const isFirstMessageLocal = isLocalMessageId(messageIds[0]); if (isFirstMessageLocal) { return; } diff --git a/src/config.ts b/src/config.ts index 770278f16..20783622b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -139,7 +139,6 @@ export const MOBILE_SCREEN_MAX_WIDTH = 600; // px export const MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH = 950; // px export const MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT = 450; // px -export const LOCAL_MESSAGE_MIN_ID = 5e9; export const MAX_INT_32 = 2 ** 31 - 1; export const TMP_CHAT_ID = '0'; diff --git a/src/global/helpers/messages.ts b/src/global/helpers/messages.ts index 4c7fd8108..f551f945c 100644 --- a/src/global/helpers/messages.ts +++ b/src/global/helpers/messages.ts @@ -6,7 +6,6 @@ import type { LangFn } from '../../hooks/useLang'; import { CONTENT_NOT_SUPPORTED, - LOCAL_MESSAGE_MIN_ID, RE_LINK_TEMPLATE, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; @@ -199,7 +198,7 @@ export function isMessageLocal(message: ApiMessage) { } export function isLocalMessageId(id: number) { - return id > LOCAL_MESSAGE_MIN_ID; + return !Number.isInteger(id); } export function isHistoryClearMessage(message: ApiMessage) { diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index 6981171cd..30597ad75 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -12,7 +12,7 @@ import type { import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types'; import { - GENERAL_TOPIC_ID, LOCAL_MESSAGE_MIN_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, + GENERAL_TOPIC_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; import { selectChat, selectChatBot, selectChatFullInfo, selectIsChatWithSelf, @@ -46,6 +46,7 @@ import { isUserRightBanned, canSendReaction, getAllowedAttachmentOptions, + isLocalMessageId, } from '../helpers'; import { findLast } from '../../util/iteratees'; import { selectIsStickerFavorite } from './symbols'; @@ -324,7 +325,7 @@ export function selectIsViewportNewest( } // Edge case: outgoing `lastMessage` is updated with a delay to optimize animation - if (lastMessageId > LOCAL_MESSAGE_MIN_ID && !selectChatMessage(global, chatId, lastMessageId)) { + if (isLocalMessageId(lastMessageId) && !selectChatMessage(global, chatId, lastMessageId)) { return true; }