diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index d444d27af..3ff76dd4f 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -36,6 +36,7 @@ export interface ApiOnProgress { } export interface ApiAttachment { + blob: Blob; blobUrl: string; compressedBlobUrl?: string; filename: string; @@ -62,6 +63,7 @@ export interface ApiAttachment { uniqueId?: string; ttlSeconds?: number; + shouldSendInHighQuality?: boolean; } export interface ApiWallpaper { diff --git a/src/assets/font-icons/hd-photo.svg b/src/assets/font-icons/hd-photo.svg new file mode 100644 index 000000000..5a24da81b --- /dev/null +++ b/src/assets/font-icons/hd-photo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/font-icons/sd-photo.svg b/src/assets/font-icons/sd-photo.svg new file mode 100644 index 000000000..d71450b1e --- /dev/null +++ b/src/assets/font-icons/sd-photo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index fbc9832b7..dedab41c7 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -2017,6 +2017,8 @@ "ValueGiftSortByNumber" = "Number"; "ResellGiftsNoFound" = "No gifts found"; "ResellGiftsClearFilters" = "Clear Filters"; +"SendInStandardQuality" = "Send In Standard Quality"; +"SendInHighQuality" = "Send In High Quality"; "MonoforumBadge" = "DIRECT"; "MonoforumStatus" = "Channel messages"; "MonoforumComposerPlaceholder" = "Choose a message to reply"; diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 530009245..bd58ebaba 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -635,7 +635,6 @@ const Composer: FC = ({ }); const { - shouldSuggestCompression, shouldForceCompression, shouldForceAsFile, handleAppendFiles, @@ -655,6 +654,7 @@ const Composer: FC = ({ canSendDocuments, insertNextText, editedMessage: editingMessage, + shouldSendInHighQuality: attachmentSettings.shouldSendInHighQuality, }); const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag(); @@ -1848,7 +1848,6 @@ const Composer: FC = ({ attachments={attachments} getHtml={getHtml} isReady={isReady} - shouldSuggestCompression={shouldSuggestCompression} shouldForceCompression={shouldForceCompression} shouldForceAsFile={shouldForceAsFile} isForCurrentMessageList={isForCurrentMessageList} diff --git a/src/components/middle/composer/AttachMenu.tsx b/src/components/middle/composer/AttachMenu.tsx index f13b269ee..bfeddbf9a 100644 --- a/src/components/middle/composer/AttachMenu.tsx +++ b/src/components/middle/composer/AttachMenu.tsx @@ -3,6 +3,7 @@ import { memo, useEffect, useMemo, } from '../../../lib/teact/teact'; +import { getActions } from '../../../global'; import type { ApiAttachMenuPeerType, ApiMessage } from '../../../api/types'; import type { GlobalState } from '../../../global/types'; @@ -55,7 +56,7 @@ export type OwnProps = { peerType?: ApiAttachMenuPeerType; shouldCollectDebugLogs?: boolean; theme: ThemeKey; - onFileSelect: (files: File[], shouldSuggestCompression?: boolean) => void; + onFileSelect: (files: File[]) => void; onPollCreate: NoneToVoidFunction; onMenuOpen: NoneToVoidFunction; onMenuClose: NoneToVoidFunction; @@ -89,6 +90,9 @@ const AttachMenu: FC = ({ messageListType, paidMessagesStars, }) => { + const { + updateAttachmentSettings, + } = getActions(); const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag(); const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isAttachMenuOpen, closeAttachMenu); @@ -126,29 +130,31 @@ const AttachMenu: FC = ({ } }); - const handleFileSelect = useLastCallback((e: Event, shouldSuggestCompression?: boolean) => { + const handleFileSelect = useLastCallback((e: Event) => { const { files } = e.target as HTMLInputElement; const validatedFiles = validateFiles(files); if (validatedFiles?.length) { - onFileSelect(validatedFiles, shouldSuggestCompression); + onFileSelect(validatedFiles); } }); const handleQuickSelect = useLastCallback(() => { + updateAttachmentSettings({ shouldCompress: true }); openSystemFilesDialog( Array.from(canSendVideoAndPhoto ? CONTENT_TYPES_WITH_PREVIEW : ( canSendPhotos ? SUPPORTED_PHOTO_CONTENT_TYPES : SUPPORTED_VIDEO_CONTENT_TYPES )).join(','), - (e) => handleFileSelect(e, true), + (e) => handleFileSelect(e), ); }); const handleDocumentSelect = useLastCallback(() => { + updateAttachmentSettings({ shouldCompress: false }); openSystemFilesDialog(!canSendDocuments && canSendAudios ? Array.from(SUPPORTED_AUDIO_CONTENT_TYPES).join(',') : ( '*' - ), (e) => handleFileSelect(e, false)); + ), (e) => handleFileSelect(e)); }); const handleSendLogs = useLastCallback(() => { diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index ca86e3b23..fafec7638 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -75,7 +75,6 @@ export type OwnProps = { isReady: boolean; isForMessage?: boolean; shouldSchedule?: boolean; - shouldSuggestCompression?: boolean; shouldForceCompression?: boolean; shouldForceAsFile?: boolean; isForCurrentMessageList?: boolean; @@ -113,6 +112,7 @@ type StateProps = { const ATTACHMENT_MODAL_INPUT_ID = 'caption-input-text'; const DROP_LEAVE_TIMEOUT_MS = 150; const MAX_LEFT_CHARS_TO_SHOW = 100; +const CLOSE_MENU_ANIMATION_DURATION = 200; const AttachmentModal: FC = ({ chatId, @@ -134,7 +134,6 @@ const AttachmentModal: FC = ({ shouldSuggestCustomEmoji, customEmojiForEmoji, attachmentSettings, - shouldSuggestCompression, shouldForceCompression, shouldForceAsFile, isForCurrentMessageList, @@ -176,14 +175,16 @@ const AttachmentModal: FC = ({ const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag(); - const [shouldSendCompressed, setShouldSendCompressed] = useState( - shouldSuggestCompression ?? attachmentSettings.shouldCompress, - ); + const shouldSendCompressed = attachmentSettings.shouldCompress; const isSendingCompressed = Boolean( (shouldSendCompressed || shouldForceCompression || isInAlbum) && !shouldForceAsFile, ); const [shouldSendGrouped, setShouldSendGrouped] = useState(attachmentSettings.shouldSendGrouped); const isInvertedMedia = attachmentSettings.isInvertedMedia; + const [shouldSendInHighQuality, setShouldSendInHighQuality] = useState( + attachmentSettings.shouldSendInHighQuality, + ); + const [renderingShouldSendInHighQuality, setRenderingShouldSendInHighQuality] = useState(shouldSendInHighQuality); const { handleScroll: handleAttachmentsScroll, @@ -197,6 +198,8 @@ const AttachmentModal: FC = ({ const renderingIsOpen = Boolean(renderingAttachments?.length); const [isHovered, markHovered, unmarkHovered] = useFlag(); + const timerRef = useRef(); + useEffect(() => { if (!isOpen) { closeSymbolMenu(); @@ -269,16 +272,16 @@ const AttachmentModal: FC = ({ useEffect(() => { if (isOpen) { - setShouldSendCompressed(shouldSuggestCompression ?? attachmentSettings.shouldCompress); setShouldSendGrouped(attachmentSettings.shouldSendGrouped); + setShouldSendInHighQuality(attachmentSettings.shouldSendInHighQuality); } - }, [attachmentSettings, isOpen, shouldSuggestCompression]); + }, [attachmentSettings, isOpen]); useEffect(() => { if (!isOpen) { updateAttachmentSettings({ isInvertedMedia: undefined }); } - }, [updateAttachmentSettings, isOpen, shouldSuggestCompression]); + }, [updateAttachmentSettings, isOpen]); function setIsInvertedMedia(value?: true) { updateAttachmentSettings({ isInvertedMedia: value }); @@ -303,9 +306,10 @@ const AttachmentModal: FC = ({ : isSilent ? onSendSilent : onSend; send(isSendingCompressed, shouldSendGrouped, isInvertedMedia); updateAttachmentSettings({ - shouldCompress: shouldSuggestCompression === undefined ? isSendingCompressed : undefined, + shouldCompress: isSendingCompressed, shouldSendGrouped, isInvertedMedia, + shouldSendInHighQuality, }); } }); @@ -387,6 +391,17 @@ const AttachmentModal: FC = ({ }))); }); + const handleToggleShouldCompress = useLastCallback(() => { + const newValue = !shouldSendCompressed; + updateAttachmentSettings({ shouldCompress: newValue }); + }); + + const handleToggleQuality = useLastCallback(() => { + const newValue = !shouldSendInHighQuality; + setShouldSendInHighQuality(newValue); + updateAttachmentSettings({ shouldSendInHighQuality: newValue }); + }); + const handleDisableSpoilers = useLastCallback(() => { onAttachmentsUpdate(attachments.map((a) => ({ ...a, shouldSendAsSpoiler: undefined }))); }); @@ -458,12 +473,13 @@ const AttachmentModal: FC = ({ const isQuickGallery = isSendingCompressed && hasOnlyMedia; - const [areAllPhotos, areAllVideos, areAllAudios] = useMemo(() => { + const [areAllPhotos, areAllVideos, areAllAudios, hasAnyPhoto] = useMemo(() => { if (!isQuickGallery || !renderingAttachments) return [false, false, false]; const everyPhoto = renderingAttachments.every((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)); const everyVideo = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)); const everyAudio = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)); - return [everyPhoto, everyVideo, everyAudio]; + const hasAnyPhoto = renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType)); + return [everyPhoto, everyVideo, everyAudio, hasAnyPhoto]; }, [renderingAttachments, isQuickGallery]); const hasAnySpoilerable = useMemo(() => { @@ -471,6 +487,20 @@ const AttachmentModal: FC = ({ return renderingAttachments.some((a) => !SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType)); }, [renderingAttachments]); + useEffect(() => { + if (shouldSendInHighQuality === renderingShouldSendInHighQuality) return; + if (timerRef.current) clearTimeout(timerRef.current); + timerRef.current = window.setTimeout(() => { + setRenderingShouldSendInHighQuality(shouldSendInHighQuality); + }, CLOSE_MENU_ANIMATION_DURATION); + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = undefined; + } + }; + }, [shouldSendInHighQuality, renderingShouldSendInHighQuality]); + if (!renderingAttachments) { return undefined; } @@ -536,16 +566,24 @@ const AttachmentModal: FC = ({ { !shouldForceAsFile && !shouldForceCompression && (isSendingCompressed ? ( - setShouldSendCompressed(false)}> + {oldLang(isMultiple ? 'Attachment.SendAsFiles' : 'Attachment.SendAsFile')} ) : ( - setShouldSendCompressed(true)}> + {isMultiple ? 'Send All as Media' : 'Send as Media'} )) } + {isSendingCompressed && !editingMessage && hasAnyPhoto && ( + + {lang(renderingShouldSendInHighQuality ? 'SendInStandardQuality' : 'SendInHighQuality')} + + )} {isSendingCompressed && hasAnySpoilerable && Boolean(!editingMessage) && ( hasSpoiler ? ( diff --git a/src/components/middle/composer/DropArea.tsx b/src/components/middle/composer/DropArea.tsx index fb17f3c39..96c0de8e4 100644 --- a/src/components/middle/composer/DropArea.tsx +++ b/src/components/middle/composer/DropArea.tsx @@ -25,7 +25,7 @@ export type OwnProps = { isOpen: boolean; withQuick?: boolean; onHide: NoneToVoidFunction; - onFileSelect: (files: File[], suggestCompression?: boolean) => void; + onFileSelect: (files: File[]) => void; editingMessage?: ApiMessage | undefined; }; @@ -41,7 +41,7 @@ const DropArea: FC = ({ isOpen, withQuick, onHide, onFileSelect, editingMessage, }) => { const lang = useLang(); - const { showNotification } = getActions(); + const { showNotification, updateAttachmentSettings } = getActions(); const hideTimeoutRef = useRef(); const prevWithQuick = usePreviousDeprecated(withQuick); const { shouldRender, transitionClassNames } = useShowTransitionDeprecated(isOpen); @@ -78,7 +78,8 @@ const DropArea: FC = ({ } onHide(); - onFileSelect(files, withQuick ? false : undefined); + updateAttachmentSettings({ shouldCompress: withQuick ? false : undefined }); + onFileSelect(files); }); const handleQuickFilesDrop = useLastCallback(async (e: React.DragEvent) => { @@ -102,7 +103,8 @@ const DropArea: FC = ({ } onHide(); - onFileSelect(files, true); + updateAttachmentSettings({ shouldCompress: true }); + onFileSelect(files); } }); diff --git a/src/components/middle/composer/helpers/buildAttachment.ts b/src/components/middle/composer/helpers/buildAttachment.ts index b27be6c60..5b8348606 100644 --- a/src/components/middle/composer/helpers/buildAttachment.ts +++ b/src/components/middle/composer/helpers/buildAttachment.ts @@ -14,7 +14,8 @@ import { } from '../../../../util/files'; import { scaleImage } from '../../../../util/imageResize'; -const MAX_QUICK_IMG_SIZE = 1280; // px +const MAX_STANDARD_QUALITY_IMG_SIZE = 1280; // px +const MAX_HIGH_QUALITY_IMG_SIZE = 2560; const MAX_THUMB_IMG_SIZE = 40; // px const MAX_ASPECT_RATIO = 20; const FILE_EXT_REGEX = /\.[^/.]+$/; @@ -28,22 +29,25 @@ export default async function buildAttachment( let audio; let previewBlobUrl; let shouldSendAsFile; + const shouldSendInHighQuality = options?.shouldSendInHighQuality; if (SUPPORTED_PHOTO_CONTENT_TYPES.has(mimeType)) { const img = await preloadImage(blobUrl); const { width, height } = img; shouldSendAsFile = !validateAspectRatio(width, height); - const shouldShrink = Math.max(width, height) > MAX_QUICK_IMG_SIZE; + const maxQuickImgSize = shouldSendInHighQuality ? MAX_HIGH_QUALITY_IMG_SIZE : MAX_STANDARD_QUALITY_IMG_SIZE; + const shouldShrink = Math.max(width, height) > maxQuickImgSize; const isGif = mimeType === GIF_MIME_TYPE; if (!shouldSendAsFile) { if (!options?.compressedBlobUrl && !isGif && (shouldShrink || mimeType !== 'image/jpeg')) { const resizedUrl = await scaleImage( - blobUrl, shouldShrink ? MAX_QUICK_IMG_SIZE / Math.max(width, height) : 1, 'image/jpeg', + blobUrl, shouldShrink ? maxQuickImgSize / Math.max(width, height) : 1, 'image/jpeg', ); URL.revokeObjectURL(blobUrl); return buildAttachment(filename, blob, { + ...options, compressedBlobUrl: resizedUrl, }); } @@ -88,6 +92,7 @@ export default async function buildAttachment( } return { + blob, blobUrl, filename, mimeType, diff --git a/src/components/middle/composer/hooks/useAttachmentModal.ts b/src/components/middle/composer/hooks/useAttachmentModal.ts index 6f129201e..ad835fc6b 100644 --- a/src/components/middle/composer/hooks/useAttachmentModal.ts +++ b/src/components/middle/composer/hooks/useAttachmentModal.ts @@ -1,4 +1,4 @@ -import { useState } from '../../../../lib/teact/teact'; +import { useEffect, useState } from '../../../../lib/teact/teact'; import { getActions } from '../../../../global'; import type { ApiAttachment, ApiMessage } from '../../../../api/types'; @@ -22,6 +22,7 @@ export default function useAttachmentModal({ canSendDocuments, insertNextText, editedMessage, + shouldSendInHighQuality, }: { attachments: ApiAttachment[]; fileSizeLimit: number; @@ -34,12 +35,12 @@ export default function useAttachmentModal({ canSendDocuments?: boolean; insertNextText: VoidFunction; editedMessage: ApiMessage | undefined; + shouldSendInHighQuality?: boolean; }) { const lang = useLang(); const { openLimitReachedModal, showAllowedMessageTypesNotification, showNotification } = getActions(); const [shouldForceAsFile, setShouldForceAsFile] = useState(false); const [shouldForceCompression, setShouldForceCompression] = useState(false); - const [shouldSuggestCompression, setShouldSuggestCompression] = useState(undefined); const handleClearAttachments = useLastCallback(() => { setAttachments(MEMO_EMPTY_ARRAY); @@ -99,13 +100,14 @@ export default function useAttachmentModal({ } } else { const newAttachments = await Promise.all(files.map((file) => ( - buildAttachment(file.name, file, { shouldSendAsSpoiler: isSpoiler || undefined }) + buildAttachment(file.name, file, + { shouldSendAsSpoiler: isSpoiler || undefined, shouldSendInHighQuality }) ))); handleSetAttachments([...attachments, ...newAttachments]); } }); - const handleFileSelect = useLastCallback(async (files: File[], suggestCompression?: boolean) => { + const handleFileSelect = useLastCallback(async (files: File[]) => { if (editedMessage) { const newAttachment = await buildAttachment(files[0].name, files[0]); const canReplace = editedMessage && canReplaceMessageMedia(editedMessage, newAttachment); @@ -120,14 +122,23 @@ export default function useAttachmentModal({ handleSetAttachments([newAttachment]); } } else { - const newAttachments = await Promise.all(files.map((file) => buildAttachment(file.name, file))); + const newAttachments = await Promise.all(files.map((file) => + buildAttachment(file.name, file, { shouldSendInHighQuality }))); handleSetAttachments(newAttachments); } - setShouldSuggestCompression(suggestCompression); }); + const handleUpdateAttachmentsQuality = useLastCallback(async () => { + const newAttachments = await Promise.all(attachments.map((attachment) => + buildAttachment(attachment.filename, attachment.blob, { shouldSendInHighQuality }))); + handleSetAttachments(newAttachments); + }); + + useEffect(() => { + handleUpdateAttachmentsQuality(); + }, [shouldSendInHighQuality]); + return { - shouldSuggestCompression, handleAppendFiles, handleFileSelect, onCaptionUpdate: setHtml, diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts index 31eb74c3f..b5dfe5cde 100644 --- a/src/global/actions/ui/misc.ts +++ b/src/global/actions/ui/misc.ts @@ -529,17 +529,11 @@ addActionHandler('requestWave', (global, actions, payload): ActionReturnType => }); addActionHandler('updateAttachmentSettings', (global, actions, payload): ActionReturnType => { - const { - shouldCompress, shouldSendGrouped, isInvertedMedia, webPageMediaSize, - } = payload; - return { ...global, attachmentSettings: { - shouldCompress: shouldCompress ?? global.attachmentSettings.shouldCompress, - shouldSendGrouped: shouldSendGrouped ?? global.attachmentSettings.shouldSendGrouped, - isInvertedMedia, - webPageMediaSize, + ...global.attachmentSettings, + ...payload, }, }; }); diff --git a/src/global/initialState.ts b/src/global/initialState.ts index 24aa051dc..8df2512ea 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -180,6 +180,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { shouldSendGrouped: true, isInvertedMedia: undefined, webPageMediaSize: undefined, + shouldSendInHighQuality: false, }, scheduledMessages: { diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index 37a1a361c..1739ccb92 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -2193,6 +2193,7 @@ export interface ActionPayloads { shouldSendGrouped?: boolean; isInvertedMedia?: true; webPageMediaSize?: WebPageMediaSize; + shouldSendInHighQuality?: boolean; }; saveEffectInDraft: { diff --git a/src/global/types/globalState.ts b/src/global/types/globalState.ts index c2a26cc26..122985d1d 100644 --- a/src/global/types/globalState.ts +++ b/src/global/types/globalState.ts @@ -120,6 +120,7 @@ export type GlobalState = { shouldSendGrouped: boolean; isInvertedMedia?: true; webPageMediaSize?: WebPageMediaSize; + shouldSendInHighQuality?: boolean; }; attachMenu: { diff --git a/src/styles/icons.scss b/src/styles/icons.scss index 69a0c33c5..d828c8197 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -140,166 +140,168 @@ $icons-map: ( "grouped": "\f167", "hand-stop": "\f168", "hashtag": "\f169", - "heart-outline": "\f16a", - "heart": "\f16b", - "help": "\f16c", - "info-filled": "\f16d", - "info": "\f16e", - "install": "\f16f", - "italic": "\f170", - "key": "\f171", - "keyboard": "\f172", - "lamp": "\f173", - "language": "\f174", - "large-pause": "\f175", - "large-play": "\f176", - "link-badge": "\f177", - "link-broken": "\f178", - "link": "\f179", - "location": "\f17a", - "lock-badge": "\f17b", - "lock": "\f17c", - "logout": "\f17d", - "loop": "\f17e", - "mention": "\f17f", - "message-failed": "\f180", - "message-pending": "\f181", - "message-read": "\f182", - "message-succeeded": "\f183", - "message": "\f184", - "microphone-alt": "\f185", - "microphone": "\f186", - "monospace": "\f187", - "more-circle": "\f188", - "more": "\f189", - "move-caption-down": "\f18a", - "move-caption-up": "\f18b", - "mute": "\f18c", - "muted": "\f18d", - "my-notes": "\f18e", - "new-chat-filled": "\f18f", - "next": "\f190", - "nochannel": "\f191", - "noise-suppression": "\f192", - "non-contacts": "\f193", - "one-filled": "\f194", - "open-in-new-tab": "\f195", - "password-off": "\f196", - "pause": "\f197", - "permissions": "\f198", - "phone-discard-outline": "\f199", - "phone-discard": "\f19a", - "phone": "\f19b", - "photo": "\f19c", - "pin-badge": "\f19d", - "pin-list": "\f19e", - "pin": "\f19f", - "pinned-chat": "\f1a0", - "pinned-message": "\f1a1", - "pip": "\f1a2", - "play-story": "\f1a3", - "play": "\f1a4", - "poll": "\f1a5", - "previous": "\f1a6", - "privacy-policy": "\f1a7", - "proof-of-ownership": "\f1a8", - "quote-text": "\f1a9", - "quote": "\f1aa", - "radial-badge": "\f1ab", - "readchats": "\f1ac", - "recent": "\f1ad", - "reload": "\f1ae", - "remove-quote": "\f1af", - "remove": "\f1b0", - "reopen-topic": "\f1b1", - "replace": "\f1b2", - "replies": "\f1b3", - "reply-filled": "\f1b4", - "reply": "\f1b5", - "revenue-split": "\f1b6", - "revote": "\f1b7", - "save-story": "\f1b8", - "saved-messages": "\f1b9", - "schedule": "\f1ba", - "search": "\f1bb", - "select": "\f1bc", - "sell-outline": "\f1bd", - "sell": "\f1be", - "send-outline": "\f1bf", - "send": "\f1c0", - "settings-filled": "\f1c1", - "settings": "\f1c2", - "share-filled": "\f1c3", - "share-screen-outlined": "\f1c4", - "share-screen-stop": "\f1c5", - "share-screen": "\f1c6", - "show-message": "\f1c7", - "sidebar": "\f1c8", - "skip-next": "\f1c9", - "skip-previous": "\f1ca", - "smallscreen": "\f1cb", - "smile": "\f1cc", - "sort-by-date": "\f1cd", - "sort-by-number": "\f1ce", - "sort-by-price": "\f1cf", - "sort": "\f1d0", - "speaker-muted-story": "\f1d1", - "speaker-outline": "\f1d2", - "speaker-story": "\f1d3", - "speaker": "\f1d4", - "spoiler-disable": "\f1d5", - "spoiler": "\f1d6", - "sport": "\f1d7", - "star": "\f1d8", - "stars-lock": "\f1d9", - "stats": "\f1da", - "stealth-future": "\f1db", - "stealth-past": "\f1dc", - "stickers": "\f1dd", - "stop-raising-hand": "\f1de", - "stop": "\f1df", - "story-caption": "\f1e0", - "story-expired": "\f1e1", - "story-priority": "\f1e2", - "story-reply": "\f1e3", - "strikethrough": "\f1e4", - "tag-add": "\f1e5", - "tag-crossed": "\f1e6", - "tag-filter": "\f1e7", - "tag-name": "\f1e8", - "tag": "\f1e9", - "timer": "\f1ea", - "toncoin": "\f1eb", - "trade": "\f1ec", - "transcribe": "\f1ed", - "truck": "\f1ee", - "unarchive": "\f1ef", - "underlined": "\f1f0", - "unique-profile": "\f1f1", - "unlist-outline": "\f1f2", - "unlist": "\f1f3", - "unlock-badge": "\f1f4", - "unlock": "\f1f5", - "unmute": "\f1f6", - "unpin": "\f1f7", - "unread": "\f1f8", - "up": "\f1f9", - "user-filled": "\f1fa", - "user-online": "\f1fb", - "user": "\f1fc", - "video-outlined": "\f1fd", - "video-stop": "\f1fe", - "video": "\f1ff", - "view-once": "\f200", - "voice-chat": "\f201", - "volume-1": "\f202", - "volume-2": "\f203", - "volume-3": "\f204", - "web": "\f205", - "webapp": "\f206", - "word-wrap": "\f207", - "zoom-in": "\f208", - "zoom-out": "\f209", + "hd-photo": "\f16a", + "heart-outline": "\f16b", + "heart": "\f16c", + "help": "\f16d", + "info-filled": "\f16e", + "info": "\f16f", + "install": "\f170", + "italic": "\f171", + "key": "\f172", + "keyboard": "\f173", + "lamp": "\f174", + "language": "\f175", + "large-pause": "\f176", + "large-play": "\f177", + "link-badge": "\f178", + "link-broken": "\f179", + "link": "\f17a", + "location": "\f17b", + "lock-badge": "\f17c", + "lock": "\f17d", + "logout": "\f17e", + "loop": "\f17f", + "mention": "\f180", + "message-failed": "\f181", + "message-pending": "\f182", + "message-read": "\f183", + "message-succeeded": "\f184", + "message": "\f185", + "microphone-alt": "\f186", + "microphone": "\f187", + "monospace": "\f188", + "more-circle": "\f189", + "more": "\f18a", + "move-caption-down": "\f18b", + "move-caption-up": "\f18c", + "mute": "\f18d", + "muted": "\f18e", + "my-notes": "\f18f", + "new-chat-filled": "\f190", + "next": "\f191", + "nochannel": "\f192", + "noise-suppression": "\f193", + "non-contacts": "\f194", + "one-filled": "\f195", + "open-in-new-tab": "\f196", + "password-off": "\f197", + "pause": "\f198", + "permissions": "\f199", + "phone-discard-outline": "\f19a", + "phone-discard": "\f19b", + "phone": "\f19c", + "photo": "\f19d", + "pin-badge": "\f19e", + "pin-list": "\f19f", + "pin": "\f1a0", + "pinned-chat": "\f1a1", + "pinned-message": "\f1a2", + "pip": "\f1a3", + "play-story": "\f1a4", + "play": "\f1a5", + "poll": "\f1a6", + "previous": "\f1a7", + "privacy-policy": "\f1a8", + "proof-of-ownership": "\f1a9", + "quote-text": "\f1aa", + "quote": "\f1ab", + "radial-badge": "\f1ac", + "readchats": "\f1ad", + "recent": "\f1ae", + "reload": "\f1af", + "remove-quote": "\f1b0", + "remove": "\f1b1", + "reopen-topic": "\f1b2", + "replace": "\f1b3", + "replies": "\f1b4", + "reply-filled": "\f1b5", + "reply": "\f1b6", + "revenue-split": "\f1b7", + "revote": "\f1b8", + "save-story": "\f1b9", + "saved-messages": "\f1ba", + "schedule": "\f1bb", + "sd-photo": "\f1bc", + "search": "\f1bd", + "select": "\f1be", + "sell-outline": "\f1bf", + "sell": "\f1c0", + "send-outline": "\f1c1", + "send": "\f1c2", + "settings-filled": "\f1c3", + "settings": "\f1c4", + "share-filled": "\f1c5", + "share-screen-outlined": "\f1c6", + "share-screen-stop": "\f1c7", + "share-screen": "\f1c8", + "show-message": "\f1c9", + "sidebar": "\f1ca", + "skip-next": "\f1cb", + "skip-previous": "\f1cc", + "smallscreen": "\f1cd", + "smile": "\f1ce", + "sort-by-date": "\f1cf", + "sort-by-number": "\f1d0", + "sort-by-price": "\f1d1", + "sort": "\f1d2", + "speaker-muted-story": "\f1d3", + "speaker-outline": "\f1d4", + "speaker-story": "\f1d5", + "speaker": "\f1d6", + "spoiler-disable": "\f1d7", + "spoiler": "\f1d8", + "sport": "\f1d9", + "star": "\f1da", + "stars-lock": "\f1db", + "stats": "\f1dc", + "stealth-future": "\f1dd", + "stealth-past": "\f1de", + "stickers": "\f1df", + "stop-raising-hand": "\f1e0", + "stop": "\f1e1", + "story-caption": "\f1e2", + "story-expired": "\f1e3", + "story-priority": "\f1e4", + "story-reply": "\f1e5", + "strikethrough": "\f1e6", + "tag-add": "\f1e7", + "tag-crossed": "\f1e8", + "tag-filter": "\f1e9", + "tag-name": "\f1ea", + "tag": "\f1eb", + "timer": "\f1ec", + "toncoin": "\f1ed", + "trade": "\f1ee", + "transcribe": "\f1ef", + "truck": "\f1f0", + "unarchive": "\f1f1", + "underlined": "\f1f2", + "unique-profile": "\f1f3", + "unlist-outline": "\f1f4", + "unlist": "\f1f5", + "unlock-badge": "\f1f6", + "unlock": "\f1f7", + "unmute": "\f1f8", + "unpin": "\f1f9", + "unread": "\f1fa", + "up": "\f1fb", + "user-filled": "\f1fc", + "user-online": "\f1fd", + "user": "\f1fe", + "video-outlined": "\f1ff", + "video-stop": "\f200", + "video": "\f201", + "view-once": "\f202", + "voice-chat": "\f203", + "volume-1": "\f204", + "volume-2": "\f205", + "volume-3": "\f206", + "web": "\f207", + "webapp": "\f208", + "word-wrap": "\f209", + "zoom-in": "\f20a", + "zoom-out": "\f20b", ); .icon-active-sessions::before { @@ -617,6 +619,9 @@ $icons-map: ( .icon-hashtag::before { content: map.get($icons-map, "hashtag"); } +.icon-hd-photo::before { + content: map.get($icons-map, "hd-photo"); +} .icon-heart-outline::before { content: map.get($icons-map, "heart-outline"); } @@ -860,6 +865,9 @@ $icons-map: ( .icon-schedule::before { content: map.get($icons-map, "schedule"); } +.icon-sd-photo::before { + content: map.get($icons-map, "sd-photo"); +} .icon-search::before { content: map.get($icons-map, "search"); } diff --git a/src/styles/icons.woff b/src/styles/icons.woff index 8b8701bb9..4844820ba 100644 Binary files a/src/styles/icons.woff and b/src/styles/icons.woff differ diff --git a/src/styles/icons.woff2 b/src/styles/icons.woff2 index 685c38f3a..8bd0da8ac 100644 Binary files a/src/styles/icons.woff2 and b/src/styles/icons.woff2 differ diff --git a/src/types/icons/font.ts b/src/types/icons/font.ts index 3f4741db2..fbd04f0d3 100644 --- a/src/types/icons/font.ts +++ b/src/types/icons/font.ts @@ -104,6 +104,7 @@ export type FontIconName = | 'grouped' | 'hand-stop' | 'hashtag' + | 'hd-photo' | 'heart-outline' | 'heart' | 'help' @@ -185,6 +186,7 @@ export type FontIconName = | 'save-story' | 'saved-messages' | 'schedule' + | 'sd-photo' | 'search' | 'select' | 'sell-outline' diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 638195336..3227472dd 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1530,6 +1530,8 @@ export interface LangPair { 'ValueGiftSortByNumber': undefined; 'ResellGiftsNoFound': undefined; 'ResellGiftsClearFilters': undefined; + 'SendInStandardQuality': undefined; + 'SendInHighQuality': undefined; 'MonoforumBadge': undefined; 'MonoforumStatus': undefined; 'MonoforumComposerPlaceholder': undefined;