From e9b02557f4deb5188ecb85a467ab1dd116100e37 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 12 Nov 2021 18:45:55 +0300 Subject: [PATCH] Settings / Data & Storage: More auto-load settings (#1522) --- src/components/common/Document.tsx | 56 +++-- src/components/left/LeftColumn.tsx | 1 + src/components/left/settings/Settings.tsx | 5 + .../left/settings/SettingsDataStorage.tsx | 199 ++++++++++++++++++ .../left/settings/SettingsGeneral.tsx | 58 ----- .../left/settings/SettingsHeader.tsx | 2 + src/components/left/settings/SettingsMain.tsx | 6 + src/components/middle/message/Album.tsx | 14 +- src/components/middle/message/Message.tsx | 42 ++-- src/components/middle/message/Photo.tsx | 18 +- src/components/middle/message/RoundVideo.tsx | 10 +- src/components/middle/message/Video.tsx | 26 +-- src/components/middle/message/WebPage.tsx | 14 +- .../middle/message/_message-content.scss | 8 + src/components/ui/RangeSlider.tsx | 5 +- src/config.ts | 1 + src/global/cache.ts | 100 ++++++--- src/global/initial.ts | 21 +- src/modules/selectors/messages.ts | 106 ++++++++-- src/types/index.ts | 22 +- src/util/fallbackLangPack.ts | 16 ++ 21 files changed, 537 insertions(+), 193 deletions(-) create mode 100644 src/components/left/settings/SettingsDataStorage.tsx diff --git a/src/components/common/Document.tsx b/src/components/common/Document.tsx index 77c1bbc81..2d0131cb0 100644 --- a/src/components/common/Document.tsx +++ b/src/components/common/Document.tsx @@ -1,5 +1,5 @@ import React, { - FC, useCallback, memo, useRef, + FC, useCallback, memo, useRef, useEffect, useState, } from '../../lib/teact/teact'; import { getDispatch } from '../../lib/teact/teactn'; @@ -15,6 +15,7 @@ import { import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver'; import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress'; import useMedia from '../../hooks/useMedia'; +import useFlag from '../../hooks/useFlag'; import File from './File'; @@ -24,21 +25,27 @@ type OwnProps = { smaller?: boolean; isSelected?: boolean; isSelectable?: boolean; + canAutoLoad?: boolean; uploadProgress?: number; withDate?: boolean; datetime?: number; className?: string; sender?: string; + autoLoadFileMaxSizeMb?: number; isDownloading: boolean; onCancelUpload?: () => void; onMediaClick?: () => void; onDateClick?: (messageId: number, chatId: string) => void; }; +const BYTES_PER_MB = 1024 * 1024; + const Document: FC = ({ message, observeIntersection, smaller, + canAutoLoad, + autoLoadFileMaxSizeMb, uploadProgress, withDate, datetime, @@ -51,39 +58,62 @@ const Document: FC = ({ onDateClick, isDownloading, }) => { + const dispatch = getDispatch(); + // eslint-disable-next-line no-null/no-null const ref = useRef(null); const document = message.content.document!; - const extension = getDocumentExtension(document) || ''; const { fileName, size, timestamp } = document; - const withMediaViewer = onMediaClick && Boolean(document.mediaType); + const extension = getDocumentExtension(document) || ''; const isIntersecting = useIsIntersecting(ref, observeIntersection); - const dispatch = getDispatch(); + const [wasIntersected, markIntersected] = useFlag(); + useEffect(() => { + if (isIntersecting) { + markIntersected(); + } + }, [isIntersecting, markIntersected]); - const { loadProgress: downloadProgress } = useMediaWithLoadProgress( - getMessageMediaHash(message, 'download'), !isDownloading, undefined, undefined, undefined, true, + // Auto-loading does not use global download manager because requires additional click to save files locally + const [isLoadAllowed, setIsLoadAllowed] = useState( + canAutoLoad && (!autoLoadFileMaxSizeMb || size <= autoLoadFileMaxSizeMb * BYTES_PER_MB), ); + + const shouldDownload = Boolean(isDownloading || (isLoadAllowed && wasIntersected)); + + const documentHash = getMessageMediaHash(message, 'download'); + const { loadProgress: downloadProgress, mediaData } = useMediaWithLoadProgress( + documentHash, !shouldDownload, undefined, undefined, undefined, true, + ); + const isLoaded = Boolean(mediaData); + const { isUploading, isTransferring, transferProgress, - } = getMediaTransferState(message, uploadProgress || downloadProgress, isDownloading); + } = getMediaTransferState(message, uploadProgress || downloadProgress, shouldDownload && !isLoaded); const hasPreview = getDocumentHasPreview(document); const thumbDataUri = hasPreview ? getMessageMediaThumbDataUri(message) : undefined; const localBlobUrl = hasPreview ? document.previewBlobUrl : undefined; const previewData = useMedia(getMessageMediaHash(message, 'pictogram'), !isIntersecting); + const withMediaViewer = onMediaClick && Boolean(document.mediaType); + const handleClick = useCallback(() => { + if (isUploading) { + if (onCancelUpload) { + onCancelUpload(); + } + return; + } + if (isDownloading) { dispatch.cancelMessageMediaDownload({ message }); return; } - if (isUploading) { - if (onCancelUpload) { - onCancelUpload(); - } + if (isTransferring) { + setIsLoadAllowed(false); return; } @@ -92,7 +122,9 @@ const Document: FC = ({ } else { dispatch.downloadMessageMedia({ message }); } - }, [withMediaViewer, isUploading, isDownloading, onMediaClick, onCancelUpload, dispatch, message]); + }, [ + isUploading, isDownloading, isTransferring, withMediaViewer, onCancelUpload, dispatch, message, onMediaClick, + ]); const handleDateClick = useCallback(() => { onDateClick!(message.id, message.chatId); diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index 7ac1fa504..898af7a95 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -118,6 +118,7 @@ const LeftColumn: FC = ({ case SettingsScreens.Folders: case SettingsScreens.General: case SettingsScreens.Notifications: + case SettingsScreens.DataStorage: case SettingsScreens.Privacy: case SettingsScreens.Language: setSettingsScreen(SettingsScreens.Main); diff --git a/src/components/left/settings/Settings.tsx b/src/components/left/settings/Settings.tsx index 8767b5c8e..8990172d6 100644 --- a/src/components/left/settings/Settings.tsx +++ b/src/components/left/settings/Settings.tsx @@ -10,6 +10,7 @@ import Transition from '../../ui/Transition'; import SettingsHeader from './SettingsHeader'; import SettingsMain from './SettingsMain'; import SettingsEditProfile from './SettingsEditProfile'; +import SettingsDataStorage from './SettingsDataStorage'; import SettingsFolders from './folders/SettingsFolders'; import SettingsGeneral from './SettingsGeneral'; import SettingsGeneralBackground from './SettingsGeneralBackground'; @@ -187,6 +188,10 @@ const Settings: FC = ({ return ( ); + case SettingsScreens.DataStorage: + return ( + + ); case SettingsScreens.Privacy: return ( void; + onReset: () => void; +}; + +type StateProps = Pick; + +type DispatchProps = Pick; + +const SettingsDataStorage: FC = ({ + isActive, + onScreenSelect, + onReset, + canAutoLoadPhotoFromContacts, + canAutoLoadPhotoInPrivateChats, + canAutoLoadPhotoInGroups, + canAutoLoadPhotoInChannels, + canAutoLoadVideoFromContacts, + canAutoLoadVideoInPrivateChats, + canAutoLoadVideoInGroups, + canAutoLoadVideoInChannels, + canAutoLoadFileFromContacts, + canAutoLoadFileInPrivateChats, + canAutoLoadFileInGroups, + canAutoLoadFileInChannels, + canAutoPlayGifs, + canAutoPlayVideos, + autoLoadFileMaxSizeMb, + setSettingOption, +}) => { + const lang = useLang(); + + useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.General); + + const renderFileSizeCallback = useCallback((value: number) => { + return lang('AutodownloadSizeLimitUpTo', lang('FileSize.MB', String(AUTODOWNLOAD_FILESIZE_MB_LIMITS[value]), 'i')); + }, [lang]); + + const handleFileSizeChange = useCallback((value: number) => { + setSettingOption({ autoLoadFileMaxSizeMb: AUTODOWNLOAD_FILESIZE_MB_LIMITS[value] }); + }, [setSettingOption]); + + function renderContentSizeSlider() { + const value = AUTODOWNLOAD_FILESIZE_MB_LIMITS.indexOf(autoLoadFileMaxSizeMb); + + return ( +
+ +
+ ); + } + + function renderAutoDownloadBlock( + title: string, + key: 'Photo' | 'Video' | 'File', + canAutoLoadFromContacts: boolean, + canAutoLoadInPrivateChats: boolean, + canAutoLoadInGroups: boolean, + canAutoLoadInChannels: boolean, + ) { + return ( +
+

{title}

+ + setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })} + /> + setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })} + /> + setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })} + /> + setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })} + /> + + {key === 'File' && renderContentSizeSlider()} +
+ ); + } + + return ( +
+ {renderAutoDownloadBlock( + lang('AutoDownloadPhotosTitle'), + 'Photo', + canAutoLoadPhotoFromContacts, + canAutoLoadPhotoInPrivateChats, + canAutoLoadPhotoInGroups, + canAutoLoadPhotoInChannels, + )} + {renderAutoDownloadBlock( + lang('AutoDownloadVideosTitle'), + 'Video', + canAutoLoadVideoFromContacts, + canAutoLoadVideoInPrivateChats, + canAutoLoadVideoInGroups, + canAutoLoadVideoInChannels, + )} + {renderAutoDownloadBlock( + 'Auto-download files', // Proper translation is not available yet + 'File', + canAutoLoadFileFromContacts, + canAutoLoadFileInPrivateChats, + canAutoLoadFileInGroups, + canAutoLoadFileInChannels, + )} + +
+

{lang('AutoplayMedia')}

+ + setSettingOption({ canAutoPlayGifs: isChecked })} + /> + setSettingOption({ canAutoPlayVideos: isChecked })} + /> +
+
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + return pick(global.settings.byKey, [ + 'canAutoLoadPhotoFromContacts', + 'canAutoLoadPhotoInPrivateChats', + 'canAutoLoadPhotoInGroups', + 'canAutoLoadPhotoInChannels', + 'canAutoLoadVideoFromContacts', + 'canAutoLoadVideoInPrivateChats', + 'canAutoLoadVideoInGroups', + 'canAutoLoadVideoInChannels', + 'canAutoLoadFileFromContacts', + 'canAutoLoadFileInPrivateChats', + 'canAutoLoadFileInGroups', + 'canAutoLoadFileInChannels', + 'canAutoPlayGifs', + 'canAutoPlayVideos', + 'autoLoadFileMaxSizeMb', + ]); + }, + (setGlobal, actions): DispatchProps => pick(actions, [ + 'setSettingOption', + ]), +)(SettingsDataStorage)); diff --git a/src/components/left/settings/SettingsGeneral.tsx b/src/components/left/settings/SettingsGeneral.tsx index e6b559070..7eae1c80c 100644 --- a/src/components/left/settings/SettingsGeneral.tsx +++ b/src/components/left/settings/SettingsGeneral.tsx @@ -32,12 +32,6 @@ type StateProps = Pick = ({ messageTextSize, animationLevel, messageSendKeyCombo, - shouldAutoDownloadMediaFromContacts, - shouldAutoDownloadMediaInPrivateChats, - shouldAutoDownloadMediaInGroups, - shouldAutoDownloadMediaInChannels, - shouldAutoPlayGifs, - shouldAutoPlayVideos, shouldSuggestStickers, shouldLoopStickers, timeFormat, @@ -211,46 +199,6 @@ const SettingsGeneral: FC = ({ )} -
-

{lang('AutoDownloadMedia')}

- - setSettingOption({ shouldAutoDownloadMediaFromContacts: isChecked })} - /> - setSettingOption({ shouldAutoDownloadMediaInPrivateChats: isChecked })} - /> - setSettingOption({ shouldAutoDownloadMediaInGroups: isChecked })} - /> - setSettingOption({ shouldAutoDownloadMediaInChannels: isChecked })} - /> -
- -
-

{lang('AutoplayMedia')}

- - setSettingOption({ shouldAutoPlayGifs: isChecked })} - /> - setSettingOption({ shouldAutoPlayVideos: isChecked })} - /> -
-

{lang('AccDescrStickers')}

@@ -294,12 +242,6 @@ export default memo(withGlobal( 'messageTextSize', 'animationLevel', 'messageSendKeyCombo', - 'shouldAutoDownloadMediaFromContacts', - 'shouldAutoDownloadMediaInPrivateChats', - 'shouldAutoDownloadMediaInGroups', - 'shouldAutoDownloadMediaInChannels', - 'shouldAutoPlayGifs', - 'shouldAutoPlayVideos', 'shouldSuggestStickers', 'shouldLoopStickers', 'isSensitiveEnabled', diff --git a/src/components/left/settings/SettingsHeader.tsx b/src/components/left/settings/SettingsHeader.tsx index a92ebdf50..7bf993411 100644 --- a/src/components/left/settings/SettingsHeader.tsx +++ b/src/components/left/settings/SettingsHeader.tsx @@ -90,6 +90,8 @@ const SettingsHeader: FC = ({ return

{lang('General')}

; case SettingsScreens.Notifications: return

{lang('Notifications')}

; + case SettingsScreens.DataStorage: + return

{lang('DataSettings')}

; case SettingsScreens.Privacy: return

{lang('PrivacySettings')}

; case SettingsScreens.Language: diff --git a/src/components/left/settings/SettingsMain.tsx b/src/components/left/settings/SettingsMain.tsx index 4f1d1a6e1..89bb04782 100644 --- a/src/components/left/settings/SettingsMain.tsx +++ b/src/components/left/settings/SettingsMain.tsx @@ -79,6 +79,12 @@ const SettingsMain: FC = ({ > {lang('PrivacySettings')} + onScreenSelect(SettingsScreens.DataStorage)} + > + {lang('DataSettings')} + onScreenSelect(SettingsScreens.Folders)} diff --git a/src/components/middle/message/Album.tsx b/src/components/middle/message/Album.tsx index 308f01b45..581be4ec6 100644 --- a/src/components/middle/message/Album.tsx +++ b/src/components/middle/message/Album.tsx @@ -23,8 +23,8 @@ const VideoWithSelect = withSelectControl(Video); type OwnProps = { album: IAlbum; observeIntersection: ObserveFn; - shouldAutoLoad?: boolean; - shouldAutoPlay?: boolean; + canAutoLoad?: boolean; + canAutoPlay?: boolean; hasCustomAppendix?: boolean; lastSyncTime?: number; isOwn: boolean; @@ -43,8 +43,8 @@ type DispatchProps = Pick; const Album: FC = ({ album, observeIntersection, - shouldAutoLoad, - shouldAutoPlay, + canAutoLoad, + canAutoPlay, hasCustomAppendix, lastSyncTime, isOwn, @@ -78,7 +78,7 @@ const Album: FC = ({ id={`album-media-${message.id}`} message={message} observeIntersection={observeIntersection} - shouldAutoLoad={shouldAutoLoad} + canAutoLoad={canAutoLoad} shouldAffectAppendix={shouldAffectAppendix} uploadProgress={uploadProgress} dimensions={dimensions} @@ -94,8 +94,8 @@ const Album: FC = ({ id={`album-media-${message.id}`} message={message} observeIntersection={observeIntersection} - shouldAutoLoad={shouldAutoLoad} - shouldAutoPlay={shouldAutoPlay} + canAutoLoad={canAutoLoad} + canAutoPlay={canAutoPlay} uploadProgress={uploadProgress} lastSyncTime={lastSyncTime} dimensions={dimensions} diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 66289a81a..40817cea7 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -39,8 +39,8 @@ import { selectSender, selectForwardedSender, selectThreadTopMessageId, - selectShouldAutoLoadMedia, - selectShouldAutoPlayMedia, + selectCanAutoLoadMedia, + selectCanAutoPlayMedia, selectShouldLoopStickers, selectTheme, selectAllowedMessageActions, @@ -157,9 +157,10 @@ type StateProps = { isDownloading: boolean; threadId?: number; isPinnedList?: boolean; - shouldAutoLoadMedia?: boolean; - shouldAutoPlayMedia?: boolean; + canAutoLoadMedia?: boolean; + canAutoPlayMedia?: boolean; shouldLoopStickers?: boolean; + autoLoadFileMaxSizeMb: number; }; type DispatchProps = Pick; @@ -220,9 +221,10 @@ const Message: FC = ({ messageListType, isPinnedList, isDownloading, - shouldAutoLoadMedia, - shouldAutoPlayMedia, + canAutoLoadMedia, + canAutoPlayMedia, shouldLoopStickers, + autoLoadFileMaxSizeMb, toggleMessageSelection, clickInlineButton, disableContextMenuHint, @@ -513,8 +515,8 @@ const Message: FC = ({ album={album!} albumLayout={albumLayout!} observeIntersection={observeIntersectionForMedia} - shouldAutoLoad={shouldAutoLoadMedia} - shouldAutoPlay={shouldAutoPlayMedia} + canAutoLoad={canAutoLoadMedia} + canAutoPlay={canAutoPlayMedia} isOwn={isOwn} hasCustomAppendix={hasCustomAppendix} lastSyncTime={lastSyncTime} @@ -526,7 +528,7 @@ const Message: FC = ({ message={message} observeIntersection={observeIntersectionForMedia} noAvatars={noAvatars} - shouldAutoLoad={shouldAutoLoadMedia} + canAutoLoad={canAutoLoadMedia} uploadProgress={uploadProgress} shouldAffectAppendix={hasCustomAppendix} onClick={handleMediaClick} @@ -539,8 +541,7 @@ const Message: FC = ({ @@ -550,8 +551,8 @@ const Message: FC = ({ message={message} observeIntersection={observeIntersectionForMedia} noAvatars={noAvatars} - shouldAutoLoad={shouldAutoLoadMedia} - shouldAutoPlay={shouldAutoPlayMedia} + canAutoLoad={canAutoLoadMedia} + canAutoPlay={canAutoPlayMedia} uploadProgress={uploadProgress} lastSyncTime={lastSyncTime} onClick={handleMediaClick} @@ -578,6 +579,8 @@ const Message: FC = ({ = ({ message={message} observeIntersection={observeIntersectionForMedia} noAvatars={noAvatars} - shouldAutoLoad={shouldAutoLoadMedia} - shouldAutoPlay={shouldAutoPlayMedia} + canAutoLoad={canAutoLoadMedia} + canAutoPlay={canAutoPlayMedia} lastSyncTime={lastSyncTime} onMediaClick={handleMediaClick} onCancelMediaTransfer={handleCancelUpload} @@ -825,7 +828,7 @@ export default memo(withGlobal( const forceSenderName = !isChatWithSelf && isAnonymousOwnMessage(message); const canShowSender = withSenderName || withAvatar || forceSenderName; - const sender = canShowSender ? selectSender(global, message) : undefined; + const sender = selectSender(global, message); const originSender = selectForwardedSender(global, message); const botSender = viaBotId ? selectUser(global, viaBotId) : undefined; @@ -869,7 +872,7 @@ export default memo(withGlobal( theme: selectTheme(global), chatUsername, forceSenderName, - sender, + sender: canShowSender ? sender : undefined, originSender, botSender, shouldHideReply, @@ -894,8 +897,9 @@ export default memo(withGlobal( threadId, isDownloading, isPinnedList: messageListType === 'pinned', - shouldAutoLoadMedia: chat ? selectShouldAutoLoadMedia(global, message, chat, sender) : undefined, - shouldAutoPlayMedia: selectShouldAutoPlayMedia(global, message), + canAutoLoadMedia: chat ? selectCanAutoLoadMedia(global, message, chat, sender) : undefined, + canAutoPlayMedia: selectCanAutoPlayMedia(global, message), + autoLoadFileMaxSizeMb: global.settings.byKey.autoLoadFileMaxSizeMb, shouldLoopStickers: selectShouldLoopStickers(global), ...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }), ...(typeof uploadProgress === 'number' && { uploadProgress }), diff --git a/src/components/middle/message/Photo.tsx b/src/components/middle/message/Photo.tsx index 2ffb49ee3..6ade62676 100644 --- a/src/components/middle/message/Photo.tsx +++ b/src/components/middle/message/Photo.tsx @@ -18,19 +18,19 @@ import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress'; import useShowTransition from '../../../hooks/useShowTransition'; import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef'; import usePrevious from '../../../hooks/usePrevious'; +import useMediaTransition from '../../../hooks/useMediaTransition'; import buildClassName from '../../../util/buildClassName'; import getCustomAppendixBg from './helpers/getCustomAppendixBg'; import { calculateMediaDimensions } from './helpers/mediaDimensions'; import ProgressSpinner from '../../ui/ProgressSpinner'; -import useMediaTransition from '../../../hooks/useMediaTransition'; export type OwnProps = { id?: string; message: ApiMessage; observeIntersection?: ObserveFn; noAvatars?: boolean; - shouldAutoLoad?: boolean; + canAutoLoad?: boolean; isInSelectMode?: boolean; isSelected?: boolean; uploadProgress?: number; @@ -51,7 +51,7 @@ const Photo: FC = ({ message, observeIntersection, noAvatars, - shouldAutoLoad, + canAutoLoad, isInSelectMode, isSelected, uploadProgress, @@ -72,7 +72,7 @@ const Photo: FC = ({ const isIntersecting = useIsIntersecting(ref, observeIntersection); - const [isLoadAllowed, setIsLoadAllowed] = useState(shouldAutoLoad); + const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad); const shouldLoad = isLoadAllowed && isIntersecting; const { mediaData, loadProgress, @@ -98,6 +98,10 @@ const Photo: FC = ({ shouldRender: shouldRenderSpinner, transitionClassNames: spinnerClassNames, } = useShowTransition(isTransferring, undefined, wasLoadDisabled, 'slow'); + const { + shouldRender: shouldRenderDownloadButton, + transitionClassNames: downloadButtonClassNames, + } = useShowTransition(!fullMediaData && !isLoadAllowed); const handleClick = useCallback(() => { if (isUploading) { @@ -164,14 +168,12 @@ const Photo: FC = ({ height={height} alt="" /> - {shouldRenderSpinner && ( + {shouldRenderSpinner && !shouldRenderDownloadButton && (
)} - {!fullMediaData && !isLoadAllowed && ( - - )} + {shouldRenderDownloadButton && } {isTransferring && ( {Math.round(transferProgress * 100)}% )} diff --git a/src/components/middle/message/RoundVideo.tsx b/src/components/middle/message/RoundVideo.tsx index 7ec801a36..e0bcde77d 100644 --- a/src/components/middle/message/RoundVideo.tsx +++ b/src/components/middle/message/RoundVideo.tsx @@ -34,8 +34,7 @@ import './RoundVideo.scss'; type OwnProps = { message: ApiMessage; observeIntersection: ObserveFn; - shouldAutoLoad?: boolean; - shouldAutoPlay?: boolean; + canAutoLoad?: boolean; lastSyncTime?: number; isDownloading?: boolean; }; @@ -55,8 +54,7 @@ function createCapture(onRelease: NoneToVoidFunction) { const RoundVideo: FC = ({ message, observeIntersection, - shouldAutoLoad, - shouldAutoPlay, + canAutoLoad, lastSyncTime, isDownloading, }) => { @@ -71,7 +69,7 @@ const RoundVideo: FC = ({ const isIntersecting = useIsIntersecting(ref, observeIntersection); - const [isLoadAllowed, setIsLoadAllowed] = useState(shouldAutoLoad && shouldAutoPlay); + const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad); const shouldLoad = Boolean(isLoadAllowed && isIntersecting && lastSyncTime); const { mediaData, loadProgress } = useMediaWithLoadProgress( getMessageMediaHash(message, 'inline'), @@ -243,7 +241,7 @@ const RoundVideo: FC = ({
)} {!mediaData && !isLoadAllowed && ( - + )}
{isActivated ? formatMediaDuration(playerRef.current!.currentTime) : formatMediaDuration(video.duration)} diff --git a/src/components/middle/message/Video.tsx b/src/components/middle/message/Video.tsx index 400a9cb9b..e1de08b36 100644 --- a/src/components/middle/message/Video.tsx +++ b/src/components/middle/message/Video.tsx @@ -36,8 +36,8 @@ export type OwnProps = { message: ApiMessage; observeIntersection: ObserveFn; noAvatars?: boolean; - shouldAutoLoad?: boolean; - shouldAutoPlay?: boolean; + canAutoLoad?: boolean; + canAutoPlay?: boolean; uploadProgress?: number; dimensions?: IMediaDimensions; lastSyncTime?: number; @@ -51,8 +51,8 @@ const Video: FC = ({ message, observeIntersection, noAvatars, - shouldAutoLoad, - shouldAutoPlay, + canAutoLoad, + canAutoPlay, uploadProgress, lastSyncTime, dimensions, @@ -70,9 +70,9 @@ const Video: FC = ({ const isIntersecting = useIsIntersecting(ref, observeIntersection); - const [isLoadAllowed, setIsLoadAllowed] = useState(shouldAutoLoad); + const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad); const shouldLoad = Boolean(isLoadAllowed && isIntersecting && lastSyncTime); - const [isPlayAllowed, setIsPlayAllowed] = useState(shouldAutoPlay); + const [isPlayAllowed, setIsPlayAllowed] = useState(canAutoPlay); const previewBlobUrl = useMedia( getMessageMediaHash(message, 'pictogram'), @@ -105,7 +105,7 @@ const Video: FC = ({ lastSyncTime, ); - const { isBuffered, bufferingHandlers } = useBuffering(!shouldAutoLoad); + const { isBuffered, bufferingHandlers } = useBuffering(!canAutoLoad); const { isUploading, isTransferring, transferProgress } = getMediaTransferState( message, uploadProgress || (isDownloading ? downloadProgress : loadProgress), @@ -116,6 +116,10 @@ const Video: FC = ({ shouldRender: shouldRenderSpinner, transitionClassNames: spinnerClassNames, } = useShowTransition(isTransferring, undefined, wasLoadDisabled); + const { + shouldRender: shouldRenderPlayButton, + transitionClassNames: playButtonClassNames, + } = useShowTransition(isLoadAllowed && !isPlayAllowed && !shouldRenderSpinner); const [playProgress, setPlayProgress] = useState(0); const handleTimeUpdate = useCallback((e: React.SyntheticEvent) => { @@ -128,7 +132,7 @@ const Video: FC = ({ const isForwarded = isForwardedMessage(message); const { width, height } = dimensions || calculateVideoDimensions(video, isOwn, isForwarded, noAvatars); - useHeavyAnimationCheckForVideo(videoRef, Boolean(isInline && shouldAutoPlay)); + useHeavyAnimationCheckForVideo(videoRef, Boolean(isInline && canAutoPlay)); usePauseOnInactive(videoRef, isPlayAllowed); useVideoCleanup(videoRef, [isInline]); @@ -193,11 +197,9 @@ const Video: FC = ({ )} - {(isLoadAllowed && !isPlayAllowed && !shouldRenderSpinner) && ( - - )} + {shouldRenderPlayButton && } {shouldRenderSpinner && ( -
+
)} diff --git a/src/components/middle/message/WebPage.tsx b/src/components/middle/message/WebPage.tsx index 7a9101859..cf7507949 100644 --- a/src/components/middle/message/WebPage.tsx +++ b/src/components/middle/message/WebPage.tsx @@ -22,8 +22,8 @@ type OwnProps = { message: ApiMessage; observeIntersection?: ObserveFn; noAvatars?: boolean; - shouldAutoLoad?: boolean; - shouldAutoPlay?: boolean; + canAutoLoad?: boolean; + canAutoPlay?: boolean; inPreview?: boolean; lastSyncTime?: number; isDownloading?: boolean; @@ -36,8 +36,8 @@ const WebPage: FC = ({ message, observeIntersection, noAvatars, - shouldAutoLoad, - shouldAutoPlay, + canAutoLoad, + canAutoPlay, inPreview, lastSyncTime, isDownloading = false, @@ -91,7 +91,7 @@ const WebPage: FC = ({ message={message} observeIntersection={observeIntersection} noAvatars={noAvatars} - shouldAutoLoad={shouldAutoLoad} + canAutoLoad={canAutoLoad} size={isSquarePhoto ? 'pictogram' : 'inline'} nonInteractive={!isMediaInteractive} onClick={isMediaInteractive ? handleMediaClick : undefined} @@ -114,8 +114,8 @@ const WebPage: FC = ({ message={message} observeIntersection={observeIntersection!} noAvatars={noAvatars} - shouldAutoLoad={shouldAutoLoad} - shouldAutoPlay={shouldAutoPlay} + canAutoLoad={canAutoLoad} + canAutoPlay={canAutoPlay} lastSyncTime={lastSyncTime} onClick={isMediaInteractive ? handleMediaClick : undefined} onCancelUpload={onCancelMediaTransfer} diff --git a/src/components/middle/message/_message-content.scss b/src/components/middle/message/_message-content.scss index 9c38ffa31..a667d1b7d 100644 --- a/src/components/middle/message/_message-content.scss +++ b/src/components/middle/message/_message-content.scss @@ -495,6 +495,14 @@ border-radius: 50%; background: rgba(black, 0.25); } + + &.opacity-transition { + transition: opacity .15s ease; + + &:not(.open) { + opacity: 0; + } + } } .icon-download { diff --git a/src/components/ui/RangeSlider.tsx b/src/components/ui/RangeSlider.tsx index 65397ede2..1e1c90ddf 100644 --- a/src/components/ui/RangeSlider.tsx +++ b/src/components/ui/RangeSlider.tsx @@ -15,6 +15,7 @@ type OwnProps = { step?: number; label?: string; value: number; + renderValue?: (value: number) => string; disabled?: boolean; onChange: (value: number) => void; }; @@ -26,6 +27,7 @@ const RangeSlider: FC = ({ step = 1, label, value, + renderValue, disabled, onChange, }) => { @@ -46,7 +48,6 @@ const RangeSlider: FC = ({ const possibleValuesLength = (max - min) / step; return ((value - min) / possibleValuesLength) * 100; } - return 0; }, [options, value, max, min, step]); return ( @@ -55,7 +56,7 @@ const RangeSlider: FC = ({
{label} {!options && ( - {value} + {renderValue ? renderValue(value) : value} )}
)} diff --git a/src/config.ts b/src/config.ts index 288f57587..0225f148b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -32,6 +32,7 @@ export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg'; export const LANG_CACHE_NAME = 'tt-lang-packs-v6'; export const ASSET_CACHE_NAME = 'tt-assets'; +export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500]; export const DOWNLOAD_WORKERS = 16; export const UPLOAD_WORKERS = 16; diff --git a/src/global/cache.ts b/src/global/cache.ts index 461333d10..2bc9908e5 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -107,40 +107,7 @@ function readCache(initialState: GlobalState): GlobalState { } if (cached) { - // Pre-fill defaults in nested objects which may be missing in older cache - cached.settings.byKey = { - ...initialState.settings.byKey, - ...cached.settings.byKey, - }; - cached.settings.themes = { - ...initialState.settings.themes, - ...cached.settings.themes, - }; - cached.chatFolders = { - ...initialState.chatFolders, - ...cached.chatFolders, - }; - - if (!cached.stickers.greeting) { - cached.stickers.greeting = initialState.stickers.greeting; - } - - if (!cached.activeDownloads) { - cached.activeDownloads = { - byChatId: {}, - }; - } - - if (!cached.serviceNotifications) { - cached.serviceNotifications = []; - } - - if (cached.audioPlayer.volume === undefined) { - cached.audioPlayer.volume = DEFAULT_VOLUME; - } - if (cached.audioPlayer.playbackRate === undefined) { - cached.audioPlayer.playbackRate = DEFAULT_PLAYBACK_RATE; - } + migrateCache(cached, initialState); } const newState = { @@ -159,6 +126,71 @@ function readCache(initialState: GlobalState): GlobalState { }; } +function migrateCache(cached: GlobalState, initialState: GlobalState) { + if ('shouldAutoDownloadMediaFromContacts' in cached.settings.byKey) { + const { + shouldAutoDownloadMediaFromContacts, + shouldAutoDownloadMediaInPrivateChats, + shouldAutoDownloadMediaInGroups, + shouldAutoDownloadMediaInChannels, + shouldAutoPlayVideos, + shouldAutoPlayGifs, + ...rest + } = cached.settings.byKey; + + cached.settings.byKey = { + ...rest, + canAutoLoadPhotoFromContacts: shouldAutoDownloadMediaFromContacts, + canAutoLoadVideoFromContacts: shouldAutoDownloadMediaFromContacts, + canAutoLoadPhotoInPrivateChats: shouldAutoDownloadMediaInPrivateChats, + canAutoLoadVideoInPrivateChats: shouldAutoDownloadMediaInPrivateChats, + canAutoLoadPhotoInGroups: shouldAutoDownloadMediaInGroups, + canAutoLoadVideoInGroups: shouldAutoDownloadMediaInGroups, + canAutoLoadPhotoInChannels: shouldAutoDownloadMediaInChannels, + canAutoLoadVideoInChannels: shouldAutoDownloadMediaInChannels, + canAutoPlayVideos: shouldAutoPlayVideos, + canAutoPlayGifs: shouldAutoPlayGifs, + }; + } + + cached.settings.byKey = { + ...initialState.settings.byKey, + ...cached.settings.byKey, + }; + + cached.settings.themes = { + ...initialState.settings.themes, + ...cached.settings.themes, + }; + + cached.chatFolders = { + ...initialState.chatFolders, + ...cached.chatFolders, + }; + + if (!cached.stickers.greeting) { + cached.stickers.greeting = initialState.stickers.greeting; + } + + if (!cached.activeDownloads) { + cached.activeDownloads = { + byChatId: {}, + }; + } + + if (!cached.serviceNotifications) { + cached.serviceNotifications = []; + } + + if (cached.audioPlayer.volume === undefined) { + cached.audioPlayer.volume = DEFAULT_VOLUME; + } + + if (cached.audioPlayer.playbackRate === undefined) { + cached.audioPlayer.playbackRate = DEFAULT_PLAYBACK_RATE; + } +} + function updateCache() { if (!isCaching || isHeavyAnimating) { return; diff --git a/src/global/initial.ts b/src/global/initial.ts index 45d944e1c..7abdc61b2 100644 --- a/src/global/initial.ts +++ b/src/global/initial.ts @@ -139,15 +139,24 @@ export const INITIAL_STATE: GlobalState = { : (IS_MAC_OS ? MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX : DEFAULT_MESSAGE_TEXT_SIZE_PX), animationLevel: ANIMATION_LEVEL_DEFAULT, messageSendKeyCombo: 'enter', - shouldAutoDownloadMediaFromContacts: true, - shouldAutoDownloadMediaInPrivateChats: true, - shouldAutoDownloadMediaInGroups: true, - shouldAutoDownloadMediaInChannels: true, + canAutoLoadPhotoFromContacts: true, + canAutoLoadPhotoInPrivateChats: true, + canAutoLoadPhotoInGroups: true, + canAutoLoadPhotoInChannels: true, + canAutoLoadVideoFromContacts: true, + canAutoLoadVideoInPrivateChats: true, + canAutoLoadVideoInGroups: true, + canAutoLoadVideoInChannels: true, + canAutoLoadFileFromContacts: false, + canAutoLoadFileInPrivateChats: false, + canAutoLoadFileInGroups: false, + canAutoLoadFileInChannels: false, + autoLoadFileMaxSizeMb: 10, hasWebNotifications: true, hasPushNotifications: true, notificationSoundVolume: 5, - shouldAutoPlayGifs: true, - shouldAutoPlayVideos: true, + canAutoPlayGifs: true, + canAutoPlayVideos: true, shouldSuggestStickers: true, shouldLoopStickers: true, language: 'en', diff --git a/src/modules/selectors/messages.ts b/src/modules/selectors/messages.ts index 0ab797230..e5725862c 100644 --- a/src/modules/selectors/messages.ts +++ b/src/modules/selectors/messages.ts @@ -30,6 +30,11 @@ import { isChatSuperGroup, getMessageVideo, getMessageWebPageVideo, + getMessagePhoto, + getMessageAudio, + getMessageVoice, + getMessageDocument, + getMessageWebPagePhoto, } from '../helpers'; import { findLast } from '../../util/iteratees'; import { selectIsStickerFavorite } from './symbols'; @@ -732,41 +737,110 @@ export function selectNewestMessageWithBotKeyboardButtons( return messageId ? chatMessages[messageId] : undefined; } -export function selectShouldAutoLoadMedia( +export function selectCanAutoLoadMedia( global: GlobalState, message: ApiMessage, chat: ApiChat, sender?: ApiChat | ApiUser, ) { + const isPhoto = Boolean(getMessagePhoto(message) || getMessageWebPagePhoto(message)); + const isVideo = Boolean(getMessageVideo(message) || getMessageWebPageVideo(message)); + const isFile = Boolean(getMessageAudio(message) || getMessageVoice(message) || getMessageDocument(message)); + const { - shouldAutoDownloadMediaFromContacts, - shouldAutoDownloadMediaInPrivateChats, - shouldAutoDownloadMediaInGroups, - shouldAutoDownloadMediaInChannels, + canAutoLoadPhotoFromContacts, + canAutoLoadPhotoInPrivateChats, + canAutoLoadPhotoInGroups, + canAutoLoadPhotoInChannels, + canAutoLoadVideoFromContacts, + canAutoLoadVideoInPrivateChats, + canAutoLoadVideoInGroups, + canAutoLoadVideoInChannels, + canAutoLoadFileFromContacts, + canAutoLoadFileInPrivateChats, + canAutoLoadFileInGroups, + canAutoLoadFileInChannels, } = global.settings.byKey; + if (isPhoto) { + return canAutoLoadMedia({ + global, + chat, + sender, + canAutoLoadMediaFromContacts: canAutoLoadPhotoFromContacts, + canAutoLoadMediaInPrivateChats: canAutoLoadPhotoInPrivateChats, + canAutoLoadMediaInGroups: canAutoLoadPhotoInGroups, + canAutoLoadMediaInChannels: canAutoLoadPhotoInChannels, + }); + } + + if (isVideo) { + return canAutoLoadMedia({ + global, + chat, + sender, + canAutoLoadMediaFromContacts: canAutoLoadVideoFromContacts, + canAutoLoadMediaInPrivateChats: canAutoLoadVideoInPrivateChats, + canAutoLoadMediaInGroups: canAutoLoadVideoInGroups, + canAutoLoadMediaInChannels: canAutoLoadVideoInChannels, + }); + } + + if (isFile) { + return canAutoLoadMedia({ + global, + chat, + sender, + canAutoLoadMediaFromContacts: canAutoLoadFileFromContacts, + canAutoLoadMediaInPrivateChats: canAutoLoadFileInPrivateChats, + canAutoLoadMediaInGroups: canAutoLoadFileInGroups, + canAutoLoadMediaInChannels: canAutoLoadFileInChannels, + }); + } + + return true; +} + +function canAutoLoadMedia({ + global, + chat, + sender, + canAutoLoadMediaFromContacts, + canAutoLoadMediaInPrivateChats, + canAutoLoadMediaInGroups, + canAutoLoadMediaInChannels, +}: { + global: GlobalState; + chat: ApiChat; + canAutoLoadMediaFromContacts: boolean; + canAutoLoadMediaInPrivateChats: boolean; + canAutoLoadMediaInGroups: boolean; + canAutoLoadMediaInChannels: boolean; + sender?: ApiChat | ApiUser; +}) { + const isMediaFromContact = Boolean(sender && ( + sender.id === global.currentUserId || selectIsUserOrChatContact(global, sender) + )); + return Boolean( - (shouldAutoDownloadMediaInPrivateChats && isUserId(chat.id)) - || (shouldAutoDownloadMediaInGroups && isChatGroup(chat)) - || (shouldAutoDownloadMediaInChannels && isChatChannel(chat)) - || (shouldAutoDownloadMediaFromContacts && sender && ( - sender.id === global.currentUserId - || selectIsUserOrChatContact(global, sender) - )), + (isMediaFromContact && canAutoLoadMediaFromContacts) + || (!isMediaFromContact && canAutoLoadMediaInPrivateChats && isUserId(chat.id)) + || (canAutoLoadMediaInGroups && isChatGroup(chat)) + || (canAutoLoadMediaInChannels && isChatChannel(chat)), ); } -export function selectShouldAutoPlayMedia(global: GlobalState, message: ApiMessage) { +export function selectCanAutoPlayMedia(global: GlobalState, message: ApiMessage) { const video = getMessageVideo(message) || getMessageWebPageVideo(message); if (!video) { return undefined; } const { - shouldAutoPlayVideos, - shouldAutoPlayGifs, + canAutoPlayVideos, + canAutoPlayGifs, } = global.settings.byKey; const asGif = video.isGif || video.isRound; - return (shouldAutoPlayVideos && !asGif) || (shouldAutoPlayGifs && asGif); + return (canAutoPlayVideos && !asGif) || (canAutoPlayGifs && asGif); } export function selectShouldLoopStickers(global: GlobalState) { diff --git a/src/types/index.ts b/src/types/index.ts index b2f0b1a17..30fa669c8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -56,12 +56,21 @@ export interface ISettings extends NotifySettings, Record { messageTextSize: number; animationLevel: 0 | 1 | 2; messageSendKeyCombo: 'enter' | 'ctrl-enter'; - shouldAutoDownloadMediaFromContacts: boolean; - shouldAutoDownloadMediaInPrivateChats: boolean; - shouldAutoDownloadMediaInGroups: boolean; - shouldAutoDownloadMediaInChannels: boolean; - shouldAutoPlayGifs: boolean; - shouldAutoPlayVideos: boolean; + canAutoLoadPhotoFromContacts: boolean; + canAutoLoadPhotoInPrivateChats: boolean; + canAutoLoadPhotoInGroups: boolean; + canAutoLoadPhotoInChannels: boolean; + canAutoLoadVideoFromContacts: boolean; + canAutoLoadVideoInPrivateChats: boolean; + canAutoLoadVideoInGroups: boolean; + canAutoLoadVideoInChannels: boolean; + canAutoLoadFileFromContacts: boolean; + canAutoLoadFileInPrivateChats: boolean; + canAutoLoadFileInGroups: boolean; + canAutoLoadFileInChannels: boolean; + autoLoadFileMaxSizeMb: number; + canAutoPlayGifs: boolean; + canAutoPlayVideos: boolean; shouldSuggestStickers: boolean; shouldLoopStickers: boolean; hasPassword?: boolean; @@ -145,6 +154,7 @@ export enum SettingsScreens { Main, EditProfile, Notifications, + DataStorage, Language, General, GeneralChatBackground, diff --git a/src/util/fallbackLangPack.ts b/src/util/fallbackLangPack.ts index 2aec4732d..2d077a06b 100644 --- a/src/util/fallbackLangPack.ts +++ b/src/util/fallbackLangPack.ts @@ -476,6 +476,10 @@ export default { key: 'Notifications', value: 'Notifications', }, + DataSettings: { + key: 'DataSettings', + value: 'Data and Storage', + }, PrivacySettings: { key: 'PrivacySettings', value: 'Privacy and Security', @@ -1805,4 +1809,16 @@ export default { key: 'PleaseEnterPassword', value: 'Enter your new password', }, + AutoDownloadPhotosTitle: { + key: 'AutoDownloadPhotosTitle', + value: 'Auto-download photos', + }, + AutoDownloadVideosTitle: { + key: 'AutoDownloadVideosTitle', + value: 'Auto-download videos and GIFs', + }, + AutoDownloadFilesTitle: { + key: 'AutoDownloadFilesTitle', + value: 'Auto-download files and music', + }, } as ApiLangPack;