Media: Allow sending messages while uploading (#3170)

This commit is contained in:
Alexander Zinchuk 2023-05-28 14:32:18 +02:00
parent d25892caa4
commit a953869683
8 changed files with 33 additions and 38 deletions

View File

@ -43,7 +43,6 @@ import {
import { import {
DELETED_COMMENTS_CHANNEL_ID, DELETED_COMMENTS_CHANNEL_ID,
LOCAL_MESSAGE_MIN_ID,
SERVICE_NOTIFICATIONS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
SPONSORED_MESSAGE_CACHE_MS, SPONSORED_MESSAGE_CACHE_MS,
SUPPORTED_AUDIO_CONTENT_TYPES, SUPPORTED_AUDIO_CONTENT_TYPES,
@ -65,22 +64,14 @@ import { buildApiCallDiscardReason } from './calls';
import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage'; import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage';
import { getServerTimeOffset } from '../../../util/serverTime'; 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_MESSAGES_LIMIT = 1e6; // 1M
const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp'; const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp';
const INPUT_WAVEFORM_LENGTH = 63; const INPUT_WAVEFORM_LENGTH = 63;
let localMessageCounter = LOCAL_MESSAGE_MIN_ID; let localMessageCounter = 0;
function getNextLocalMessageId(lastMessageId = 0) {
// Local IDs need to be fractional to allow service notifications to be placed between real messages. return lastMessageId + (++localMessageCounter / LOCAL_MESSAGES_LIMIT);
// 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 currentUserId!: string; let currentUserId!: string;
@ -1308,7 +1299,7 @@ export function buildLocalMessage(
scheduledAt?: number, scheduledAt?: number,
sendAs?: ApiChat | ApiUser, sendAs?: ApiChat | ApiUser,
): ApiMessage { ): ApiMessage {
const localId = getNextLocalMessageId(); const localId = getNextLocalMessageId(chat.lastMessage?.id);
const media = attachment && buildUploadingMedia(attachment); const media = attachment && buildUploadingMedia(attachment);
const isChannel = chat.type === 'chatTypeChannel'; const isChannel = chat.type === 'chatTypeChannel';
const isForum = chat.isForum; const isForum = chat.isForum;

View File

@ -214,7 +214,7 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
return { message, users }; return { message, users };
} }
let queue = Promise.resolve(); let mediaQueue = Promise.resolve();
export function sendMessage( export function sendMessage(
{ {
@ -236,6 +236,7 @@ export function sendMessage(
shouldUpdateStickerSetOrder, shouldUpdateStickerSetOrder,
}: { }: {
chat: ApiChat; chat: ApiChat;
lastMessageId?: number;
text?: string; text?: string;
entities?: ApiMessageEntity[]; entities?: ApiMessageEntity[];
replyingTo?: number; replyingTo?: number;
@ -306,8 +307,7 @@ export function sendMessage(
}, randomId, localMessage, onProgress); }, randomId, localMessage, onProgress);
} }
const prevQueue = queue; const sendPromise = (async () => {
queue = (async () => {
let media: GramJs.TypeInputMedia | undefined; let media: GramJs.TypeInputMedia | undefined;
if (attachment) { if (attachment) {
try { try {
@ -318,7 +318,7 @@ export function sendMessage(
console.warn(err); console.warn(err);
} }
await prevQueue; await mediaQueue;
return; return;
} }
@ -337,8 +337,6 @@ export function sendMessage(
}); });
} }
await prevQueue;
const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage; const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage;
try { try {
@ -369,7 +367,7 @@ export function sendMessage(
} }
})(); })();
return queue; return sendPromise;
} }
const groupedUploads: Record<string, { const groupedUploads: Record<string, {
@ -417,8 +415,8 @@ function sendGroupedMedia(
groupIndex = groupedUploads[groupedId].counter++; groupIndex = groupedUploads[groupedId].counter++;
const prevQueue = queue; const prevMediaQueue = mediaQueue;
queue = (async () => { mediaQueue = (async () => {
let media; let media;
try { try {
media = await uploadMedia(localMessage, attachment, onProgress!); media = await uploadMedia(localMessage, attachment, onProgress!);
@ -430,7 +428,7 @@ function sendGroupedMedia(
groupedUploads[groupedId].counter--; groupedUploads[groupedId].counter--;
await prevQueue; await prevMediaQueue;
return; return;
} }
@ -440,7 +438,7 @@ function sendGroupedMedia(
media as GramJs.InputMediaUploadedPhoto | GramJs.InputMediaUploadedDocument, media as GramJs.InputMediaUploadedPhoto | GramJs.InputMediaUploadedDocument,
); );
await prevQueue; await prevMediaQueue;
if (!inputMedia) { if (!inputMedia) {
groupedUploads[groupedId].counter--; groupedUploads[groupedId].counter--;
@ -482,7 +480,7 @@ function sendGroupedMedia(
if (update) handleMultipleLocalMessagesUpdate(localMessages, update); if (update) handleMultipleLocalMessagesUpdate(localMessages, update);
})(); })();
return queue; return mediaQueue;
} }
async function fetchInputMedia( async function fetchInputMedia(

View File

@ -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 type { FC } from '../../lib/teact/teact';
import React, { memo, useRef, useState } from '../../lib/teact/teact';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment'; import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
@ -20,7 +22,7 @@ import Link from '../ui/Link';
import './File.scss'; import './File.scss';
type OwnProps = { type OwnProps = {
ref?: RefObject<HTMLDivElement>; ref?: React.RefObject<HTMLDivElement>;
name: string; name: string;
extension?: string; extension?: string;
size: number; size: number;
@ -80,6 +82,10 @@ const File: FC<OwnProps> = ({
const color = getColorFromExtension(extension); const color = getColorFromExtension(extension);
const sizeString = getFileSizeString(size); 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); const { width, height } = getDocumentThumbnailDimensions(smaller);
@ -146,7 +152,7 @@ const File: FC<OwnProps> = ({
<div className="file-title" dir="auto" title={name}>{renderText(name)}</div> <div className="file-title" dir="auto" title={name}>{renderText(name)}</div>
<div className="file-subtitle" dir="auto"> <div className="file-subtitle" dir="auto">
<span> <span>
{isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString} {subtitle}
</span> </span>
{sender && <span className="file-sender">{renderText(sender)}</span>} {sender && <span className="file-sender">{renderText(sender)}</span>}
{!sender && Boolean(timestamp) && ( {!sender && Boolean(timestamp) && (

View File

@ -22,7 +22,6 @@ import { LoadMoreDirection } from '../../types';
import { import {
ANIMATION_END_DELAY, ANIMATION_END_DELAY,
LOCAL_MESSAGE_MIN_ID,
MESSAGE_LIST_SLICE, MESSAGE_LIST_SLICE,
SERVICE_NOTIFICATIONS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
} from '../../config'; } from '../../config';
@ -58,6 +57,7 @@ import {
getDocumentMediaHash, getDocumentMediaHash,
getVideoDimensions, getVideoDimensions,
getPhotoFullDimensions, getPhotoFullDimensions,
isLocalMessageId,
} from '../../global/helpers'; } from '../../global/helpers';
import { orderBy } from '../../util/iteratees'; import { orderBy } from '../../util/iteratees';
import { DPR } from '../../util/windowEnvironment'; import { DPR } from '../../util/windowEnvironment';
@ -366,7 +366,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
} }
// Loading history while sending a message can return the same message and cause ambiguity // 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) { if (isLastMessageLocal) {
return; return;
} }

View File

@ -9,9 +9,10 @@ import { LoadMoreDirection } from '../../../types';
import type { MessageListType } from '../../../global/types'; import type { MessageListType } from '../../../global/types';
import type { Signal } from '../../../util/signals'; import type { Signal } from '../../../util/signals';
import { LOCAL_MESSAGE_MIN_ID } from '../../../config';
import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment'; import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment';
import { debounce } from '../../../util/schedulers'; import { debounce } from '../../../util/schedulers';
import { isLocalMessageId } from '../../../global/helpers';
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
import useSyncEffect from '../../../hooks/useSyncEffect'; import useSyncEffect from '../../../hooks/useSyncEffect';
import { useStateRef } from '../../../hooks/useStateRef'; 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 // 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) { if (isFirstMessageLocal) {
return; return;
} }

View File

@ -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_WIDTH = 950; // px
export const MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT = 450; // 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 MAX_INT_32 = 2 ** 31 - 1;
export const TMP_CHAT_ID = '0'; export const TMP_CHAT_ID = '0';

View File

@ -6,7 +6,6 @@ import type { LangFn } from '../../hooks/useLang';
import { import {
CONTENT_NOT_SUPPORTED, CONTENT_NOT_SUPPORTED,
LOCAL_MESSAGE_MIN_ID,
RE_LINK_TEMPLATE, RE_LINK_TEMPLATE,
SERVICE_NOTIFICATIONS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
} from '../../config'; } from '../../config';
@ -199,7 +198,7 @@ export function isMessageLocal(message: ApiMessage) {
} }
export function isLocalMessageId(id: number) { export function isLocalMessageId(id: number) {
return id > LOCAL_MESSAGE_MIN_ID; return !Number.isInteger(id);
} }
export function isHistoryClearMessage(message: ApiMessage) { export function isHistoryClearMessage(message: ApiMessage) {

View File

@ -12,7 +12,7 @@ import type {
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types'; import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
import { 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'; } from '../../config';
import { import {
selectChat, selectChatBot, selectChatFullInfo, selectIsChatWithSelf, selectChat, selectChatBot, selectChatFullInfo, selectIsChatWithSelf,
@ -46,6 +46,7 @@ import {
isUserRightBanned, isUserRightBanned,
canSendReaction, canSendReaction,
getAllowedAttachmentOptions, getAllowedAttachmentOptions,
isLocalMessageId,
} from '../helpers'; } from '../helpers';
import { findLast } from '../../util/iteratees'; import { findLast } from '../../util/iteratees';
import { selectIsStickerFavorite } from './symbols'; import { selectIsStickerFavorite } from './symbols';
@ -324,7 +325,7 @@ export function selectIsViewportNewest<T extends GlobalState>(
} }
// Edge case: outgoing `lastMessage` is updated with a delay to optimize animation // 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; return true;
} }