diff --git a/src/api/gramjs/apiBuilders/messageContent.ts b/src/api/gramjs/apiBuilders/messageContent.ts index 8d9d03071..9ff211adc 100644 --- a/src/api/gramjs/apiBuilders/messageContent.ts +++ b/src/api/gramjs/apiBuilders/messageContent.ts @@ -180,7 +180,7 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo } const { - id, mimeType, thumbs, size, attributes, + id, mimeType, thumbs, size, videoThumbs, attributes, } = document; // eslint-disable-next-line no-restricted-globals @@ -189,14 +189,16 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo } const videoAttr = attributes - .find((a: any): a is GramJs.DocumentAttributeVideo => a instanceof GramJs.DocumentAttributeVideo); + .find((a): a is GramJs.DocumentAttributeVideo => a instanceof GramJs.DocumentAttributeVideo); if (!videoAttr) { return undefined; } const gifAttr = attributes - .find((a: any): a is GramJs.DocumentAttributeAnimated => a instanceof GramJs.DocumentAttributeAnimated); + .find((a): a is GramJs.DocumentAttributeAnimated => a instanceof GramJs.DocumentAttributeAnimated); + + const hasVideoPreview = videoThumbs?.some((thumb) => thumb instanceof GramJs.VideoSize && thumb.type === 'v'); const { duration, @@ -221,6 +223,7 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo thumbnail: buildApiThumbnailFromStripped(thumbs), size: size.toJSNumber(), isSpoiler, + hasVideoPreview, ...(nosound && { noSound: true }), }; } diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index 4e658527a..e12e1a718 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -98,6 +98,7 @@ export interface ApiVideo { supportsStreaming?: boolean; isRound?: boolean; isGif?: boolean; + hasVideoPreview?: boolean; isSpoiler?: boolean; thumbnail?: ApiThumbnail; blobUrl?: string; diff --git a/src/components/common/GifButton.tsx b/src/components/common/GifButton.tsx index 0f3688c95..eae31a074 100644 --- a/src/components/common/GifButton.tsx +++ b/src/components/common/GifButton.tsx @@ -5,9 +5,8 @@ import React, { import type { ApiVideo } from '../../api/types'; import type { ObserveFn } from '../../hooks/useIntersectionObserver'; -import { ApiMediaFormat } from '../../api/types'; -import { getVideoMediaHash } from '../../global/helpers'; +import { getVideoMediaHash, getVideoPreviewMediaHash } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; @@ -55,10 +54,15 @@ const GifButton: FC = ({ const isIntersecting = useIsIntersecting(ref, observeIntersection); const loadAndPlay = isIntersecting && !isDisabled; - const previewBlobUrl = useMedia(getVideoMediaHash(gif, 'preview'), !loadAndPlay, ApiMediaFormat.BlobUrl); + const previewHash = !gif.hasVideoPreview && gif.thumbnail && getVideoMediaHash(gif, 'pictogram'); + const previewBlobUrl = useMedia(previewHash, !loadAndPlay); + const [withThumb] = useState(gif.thumbnail?.dataUri && !previewBlobUrl); const thumbRef = useCanvasBlur(gif.thumbnail?.dataUri, !withThumb); - const videoData = useMedia(getVideoMediaHash(gif, 'full'), !loadAndPlay, ApiMediaFormat.BlobUrl); + + const videoHash = getVideoPreviewMediaHash(gif) || getVideoMediaHash(gif, 'full'); + const videoData = useMedia(videoHash, !loadAndPlay); + const shouldRenderVideo = Boolean(loadAndPlay && videoData); const { isBuffered, bufferingHandlers } = useBuffering(true); const shouldRenderSpinner = loadAndPlay && !isBuffered; @@ -154,8 +158,6 @@ const GifButton: FC = ({ )} {previewBlobUrl && !isVideoReady && ( diff --git a/src/components/middle/message/Album.tsx b/src/components/middle/message/Album.tsx index b1f218efc..82e0ca2c0 100644 --- a/src/components/middle/message/Album.tsx +++ b/src/components/middle/message/Album.tsx @@ -124,6 +124,7 @@ const Album: FC = ({ onCancelUpload={handleCancelUpload} isDownloading={photo.mediaType !== 'extendedMediaPreview' && getIsDownloading(activeDownloads, photo)} theme={theme} + noSelectControls={album.isPaidMedia} /> ); } else if (video) { @@ -142,6 +143,7 @@ const Album: FC = ({ onCancelUpload={handleCancelUpload} isDownloading={video.mediaType !== 'extendedMediaPreview' && getIsDownloading(activeDownloads, video)} theme={theme} + noSelectControls={album.isPaidMedia} /> ); } diff --git a/src/components/middle/message/_message-content.scss b/src/components/middle/message/_message-content.scss index 1d3c662a0..6f0c987b1 100644 --- a/src/components/middle/message/_message-content.scss +++ b/src/components/middle/message/_message-content.scss @@ -919,7 +919,7 @@ --border-bottom-left-radius: var(--border-radius-messages-small); --border-bottom-right-radius: var(--border-radius-messages-small); - > .media-inner { + .media-inner { margin: 0 !important; margin-bottom: 0.25rem !important; } diff --git a/src/components/middle/message/hocs/withSelectControl.tsx b/src/components/middle/message/hocs/withSelectControl.tsx index 947b58396..a4b003bc6 100644 --- a/src/components/middle/message/hocs/withSelectControl.tsx +++ b/src/components/middle/message/hocs/withSelectControl.tsx @@ -17,6 +17,7 @@ import useLastCallback from '../../../../hooks/useLastCallback'; type OwnProps = (PhotoProps | VideoProps) & { clickArg: number; + noSelectControls?: boolean; }; type StateProps = { @@ -76,10 +77,10 @@ export default function withSelectControl(WrappedComponent: FC) { return memo(withGlobal>( (global, ownProps) => { - const { clickArg } = ownProps; + const { clickArg, noSelectControls } = ownProps; return { - isInSelectMode: selectIsInSelectMode(global), - isSelected: selectIsMessageSelected(global, clickArg), + isInSelectMode: !noSelectControls && selectIsInSelectMode(global), + isSelected: !noSelectControls && selectIsMessageSelected(global, clickArg), }; }, )(ComponentWithSelectControl)) as typeof ComponentWithSelectControl; diff --git a/src/components/middle/message/hooks/useInnerHandlers.ts b/src/components/middle/message/hooks/useInnerHandlers.ts index eebc3db78..7501e823d 100644 --- a/src/components/middle/message/hooks/useInnerHandlers.ts +++ b/src/components/middle/message/hooks/useInnerHandlers.ts @@ -104,7 +104,6 @@ export default function useInnerHandlers( }); const openMediaViewerWithPhotoOrVideo = useLastCallback((withDynamicLoading: boolean): void => { if (paidMedia && !paidMedia.isBought) return; - if (paidMedia) return; // TODO: Implement MV and remove this line if (withDynamicLoading) { searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: messageId }); } @@ -117,12 +116,12 @@ export default function useInnerHandlers( }); }); const handlePhotoMediaClick = useLastCallback((): void => { - const withDynamicLoading = !isScheduled; + const withDynamicLoading = !isScheduled && !paidMedia; openMediaViewerWithPhotoOrVideo(withDynamicLoading); }); const handleVideoMediaClick = useLastCallback(() => { const isGif = message.content?.video?.isGif; - const withDynamicLoading = !isGif && !isScheduled; + const withDynamicLoading = !isGif && !isScheduled && !paidMedia; openMediaViewerWithPhotoOrVideo(withDynamicLoading); }); diff --git a/src/components/modals/stars/StarsBalanceModal.tsx b/src/components/modals/stars/StarsBalanceModal.tsx index 9e5ab72aa..fd5f97814 100644 --- a/src/components/modals/stars/StarsBalanceModal.tsx +++ b/src/components/modals/stars/StarsBalanceModal.tsx @@ -10,6 +10,7 @@ import { getUserFullName } from '../../../global/helpers'; import { selectUser } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { formatCurrency } from '../../../util/formatCurrency'; +import { formatInteger } from '../../../util/textFormat'; import renderText from '../../common/helpers/renderText'; import useFlag from '../../../hooks/useFlag'; @@ -233,7 +234,7 @@ function StarTopupOption({ return (
onClick?.(option)}>
- +{option.stars} + +{formatInteger(option.stars)} {/* Switch directionality for correct order. Can't use flex because https://issues.chromium.org/issues/40249030 */}
{Array.from({ length: starsCount }).map(() => ( diff --git a/src/global/actions/apiUpdaters/messages.ts b/src/global/actions/apiUpdaters/messages.ts index 850e7127b..1eb67b0d0 100644 --- a/src/global/actions/apiUpdaters/messages.ts +++ b/src/global/actions/apiUpdaters/messages.ts @@ -699,7 +699,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } else { const content = media as MediaContent; global = updateChatMessage(global, chatId, id, { - content, + content: { + ...content, + }, }); setGlobal(global); } diff --git a/src/global/helpers/messageMedia.ts b/src/global/helpers/messageMedia.ts index c129b4a4c..19a6cc919 100644 --- a/src/global/helpers/messageMedia.ts +++ b/src/global/helpers/messageMedia.ts @@ -319,6 +319,10 @@ export function getVideoMediaHash(video: ApiVideo | ApiDocument, target: Target) } } +export function getVideoPreviewMediaHash(video: ApiVideo) { + return video.hasVideoPreview ? `document${video.id}?size=v` : undefined; +} + export function getDocumentMediaHash(document: ApiDocument, target: Target) { const base = `document${document.id}`; diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index 6f6a82723..586621be0 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -1245,7 +1245,7 @@ export function selectCanForwardMessages(global: T, chatI return messageIds .map((id) => messages[id]) - .every((message) => !hasMessageTtl(message) + .every((message) => message && !hasMessageTtl(message) && (message.isForwardingAllowed || isServiceNotificationMessage(message))); }