Settings / Data & Storage: More auto-load settings (#1522)

This commit is contained in:
Alexander Zinchuk 2021-11-12 18:45:55 +03:00
parent bd914a9024
commit e9b02557f4
21 changed files with 537 additions and 193 deletions

View File

@ -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<OwnProps> = ({
message,
observeIntersection,
smaller,
canAutoLoad,
autoLoadFileMaxSizeMb,
uploadProgress,
withDate,
datetime,
@ -51,39 +58,62 @@ const Document: FC<OwnProps> = ({
onDateClick,
isDownloading,
}) => {
const dispatch = getDispatch();
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(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<ApiMediaFormat.BlobUrl>(
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<ApiMediaFormat.BlobUrl>(
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<OwnProps> = ({
} 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);

View File

@ -118,6 +118,7 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
case SettingsScreens.Folders:
case SettingsScreens.General:
case SettingsScreens.Notifications:
case SettingsScreens.DataStorage:
case SettingsScreens.Privacy:
case SettingsScreens.Language:
setSettingsScreen(SettingsScreens.Main);

View File

@ -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<OwnProps> = ({
return (
<SettingsNotifications onScreenSelect={onScreenSelect} isActive={isScreenActive} onReset={handleReset} />
);
case SettingsScreens.DataStorage:
return (
<SettingsDataStorage onScreenSelect={onScreenSelect} isActive={isScreenActive} onReset={handleReset} />
);
case SettingsScreens.Privacy:
return (
<SettingsPrivacy

View File

@ -0,0 +1,199 @@
import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
import { withGlobal } from '../../../lib/teact/teactn';
import { GlobalActions } from '../../../global/types';
import { SettingsScreens, ISettings } from '../../../types';
import { AUTODOWNLOAD_FILESIZE_MB_LIMITS } from '../../../config';
import { pick } from '../../../util/iteratees';
import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack';
import Checkbox from '../../ui/Checkbox';
import RangeSlider from '../../ui/RangeSlider';
type OwnProps = {
isActive?: boolean;
onScreenSelect: (screen: SettingsScreens) => void;
onReset: () => void;
};
type StateProps = Pick<ISettings, (
'canAutoLoadPhotoFromContacts' |
'canAutoLoadPhotoInPrivateChats' |
'canAutoLoadPhotoInGroups' |
'canAutoLoadPhotoInChannels' |
'canAutoLoadVideoFromContacts' |
'canAutoLoadVideoInPrivateChats' |
'canAutoLoadVideoInGroups' |
'canAutoLoadVideoInChannels' |
'canAutoLoadFileFromContacts' |
'canAutoLoadFileInPrivateChats' |
'canAutoLoadFileInGroups' |
'canAutoLoadFileInChannels' |
'canAutoPlayGifs' |
'canAutoPlayVideos' |
'autoLoadFileMaxSizeMb'
)>;
type DispatchProps = Pick<GlobalActions, (
'setSettingOption'
)>;
const SettingsDataStorage: FC<OwnProps & StateProps & DispatchProps> = ({
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 (
<div className="pt-5">
<RangeSlider
label={lang('AutoDownloadMaxFileSize')}
min={0}
max={5}
value={value !== -1 ? value : 2}
renderValue={renderFileSizeCallback}
onChange={handleFileSizeChange}
/>
</div>
);
}
function renderAutoDownloadBlock(
title: string,
key: 'Photo' | 'Video' | 'File',
canAutoLoadFromContacts: boolean,
canAutoLoadInPrivateChats: boolean,
canAutoLoadInGroups: boolean,
canAutoLoadInChannels: boolean,
) {
return (
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{title}</h4>
<Checkbox
label={lang('AutoDownloadSettings.Contacts')}
checked={canAutoLoadFromContacts}
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })}
/>
<Checkbox
label={lang('AutoDownloadSettings.PrivateChats')}
checked={canAutoLoadInPrivateChats}
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })}
/>
<Checkbox
label={lang('AutoDownloadSettings.GroupChats')}
checked={canAutoLoadInGroups}
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })}
/>
<Checkbox
label={lang('AutoDownloadSettings.Channels')}
checked={canAutoLoadInChannels}
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })}
/>
{key === 'File' && renderContentSizeSlider()}
</div>
);
}
return (
<div className="settings-content custom-scroll">
{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,
)}
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AutoplayMedia')}</h4>
<Checkbox
label={lang('GifsTab2')}
checked={canAutoPlayGifs}
onCheck={(isChecked) => setSettingOption({ canAutoPlayGifs: isChecked })}
/>
<Checkbox
label={lang('DataAndStorage.Autoplay.Videos')}
checked={canAutoPlayVideos}
onCheck={(isChecked) => setSettingOption({ canAutoPlayVideos: isChecked })}
/>
</div>
</div>
);
};
export default memo(withGlobal<OwnProps>(
(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));

View File

@ -32,12 +32,6 @@ type StateProps = Pick<ISettings, (
'messageTextSize' |
'animationLevel' |
'messageSendKeyCombo' |
'shouldAutoDownloadMediaFromContacts' |
'shouldAutoDownloadMediaInPrivateChats' |
'shouldAutoDownloadMediaInGroups' |
'shouldAutoDownloadMediaInChannels' |
'shouldAutoPlayGifs' |
'shouldAutoPlayVideos' |
'shouldSuggestStickers' |
'shouldLoopStickers' |
'timeFormat'
@ -73,12 +67,6 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
messageTextSize,
animationLevel,
messageSendKeyCombo,
shouldAutoDownloadMediaFromContacts,
shouldAutoDownloadMediaInPrivateChats,
shouldAutoDownloadMediaInGroups,
shouldAutoDownloadMediaInChannels,
shouldAutoPlayGifs,
shouldAutoPlayVideos,
shouldSuggestStickers,
shouldLoopStickers,
timeFormat,
@ -211,46 +199,6 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
</div>
)}
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AutoDownloadMedia')}</h4>
<Checkbox
label={lang('Contacts')}
checked={shouldAutoDownloadMediaFromContacts}
onCheck={(isChecked) => setSettingOption({ shouldAutoDownloadMediaFromContacts: isChecked })}
/>
<Checkbox
label={lang('AutodownloadPrivateChats')}
checked={shouldAutoDownloadMediaInPrivateChats}
onCheck={(isChecked) => setSettingOption({ shouldAutoDownloadMediaInPrivateChats: isChecked })}
/>
<Checkbox
label={lang('AutodownloadGroupChats')}
checked={shouldAutoDownloadMediaInGroups}
onCheck={(isChecked) => setSettingOption({ shouldAutoDownloadMediaInGroups: isChecked })}
/>
<Checkbox
label={lang('FilterChannels')}
checked={shouldAutoDownloadMediaInChannels}
onCheck={(isChecked) => setSettingOption({ shouldAutoDownloadMediaInChannels: isChecked })}
/>
</div>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AutoplayMedia')}</h4>
<Checkbox
label={lang('GifsTab2')}
checked={shouldAutoPlayGifs}
onCheck={(isChecked) => setSettingOption({ shouldAutoPlayGifs: isChecked })}
/>
<Checkbox
label={lang('DataAndStorage.Autoplay.Videos')}
checked={shouldAutoPlayVideos}
onCheck={(isChecked) => setSettingOption({ shouldAutoPlayVideos: isChecked })}
/>
</div>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AccDescrStickers')}</h4>
@ -294,12 +242,6 @@ export default memo(withGlobal<OwnProps>(
'messageTextSize',
'animationLevel',
'messageSendKeyCombo',
'shouldAutoDownloadMediaFromContacts',
'shouldAutoDownloadMediaInPrivateChats',
'shouldAutoDownloadMediaInGroups',
'shouldAutoDownloadMediaInChannels',
'shouldAutoPlayGifs',
'shouldAutoPlayVideos',
'shouldSuggestStickers',
'shouldLoopStickers',
'isSensitiveEnabled',

View File

@ -90,6 +90,8 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
return <h3>{lang('General')}</h3>;
case SettingsScreens.Notifications:
return <h3>{lang('Notifications')}</h3>;
case SettingsScreens.DataStorage:
return <h3>{lang('DataSettings')}</h3>;
case SettingsScreens.Privacy:
return <h3>{lang('PrivacySettings')}</h3>;
case SettingsScreens.Language:

View File

@ -79,6 +79,12 @@ const SettingsMain: FC<OwnProps & StateProps & DispatchProps> = ({
>
{lang('PrivacySettings')}
</ListItem>
<ListItem
icon="data"
onClick={() => onScreenSelect(SettingsScreens.DataStorage)}
>
{lang('DataSettings')}
</ListItem>
<ListItem
icon="folder"
onClick={() => onScreenSelect(SettingsScreens.Folders)}

View File

@ -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<GlobalActions, 'cancelSendingMessage'>;
const Album: FC<OwnProps & StateProps & DispatchProps> = ({
album,
observeIntersection,
shouldAutoLoad,
shouldAutoPlay,
canAutoLoad,
canAutoPlay,
hasCustomAppendix,
lastSyncTime,
isOwn,
@ -78,7 +78,7 @@ const Album: FC<OwnProps & StateProps & DispatchProps> = ({
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<OwnProps & StateProps & DispatchProps> = ({
id={`album-media-${message.id}`}
message={message}
observeIntersection={observeIntersection}
shouldAutoLoad={shouldAutoLoad}
shouldAutoPlay={shouldAutoPlay}
canAutoLoad={canAutoLoad}
canAutoPlay={canAutoPlay}
uploadProgress={uploadProgress}
lastSyncTime={lastSyncTime}
dimensions={dimensions}

View File

@ -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<GlobalActions, 'toggleMessageSelection' | 'clickInlineButton' | 'disableContextMenuHint'>;
@ -220,9 +221,10 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
messageListType,
isPinnedList,
isDownloading,
shouldAutoLoadMedia,
shouldAutoPlayMedia,
canAutoLoadMedia,
canAutoPlayMedia,
shouldLoopStickers,
autoLoadFileMaxSizeMb,
toggleMessageSelection,
clickInlineButton,
disableContextMenuHint,
@ -513,8 +515,8 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
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<OwnProps & StateProps & DispatchProps> = ({
message={message}
observeIntersection={observeIntersectionForMedia}
noAvatars={noAvatars}
shouldAutoLoad={shouldAutoLoadMedia}
canAutoLoad={canAutoLoadMedia}
uploadProgress={uploadProgress}
shouldAffectAppendix={hasCustomAppendix}
onClick={handleMediaClick}
@ -539,8 +541,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
<RoundVideo
message={message}
observeIntersection={observeIntersectionForMedia}
shouldAutoLoad={shouldAutoLoadMedia}
shouldAutoPlay={shouldAutoPlayMedia}
canAutoLoad={canAutoLoadMedia}
lastSyncTime={lastSyncTime}
isDownloading={isDownloading}
/>
@ -550,8 +551,8 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
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<OwnProps & StateProps & DispatchProps> = ({
<Document
message={message}
observeIntersection={observeIntersectionForMedia}
canAutoLoad={canAutoLoadMedia}
autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb}
uploadProgress={uploadProgress}
isSelectable={isInDocumentGroup}
isSelected={isSelected}
@ -610,8 +613,8 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
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<OwnProps>(
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<OwnProps>(
theme: selectTheme(global),
chatUsername,
forceSenderName,
sender,
sender: canShowSender ? sender : undefined,
originSender,
botSender,
shouldHideReply,
@ -894,8 +897,9 @@ export default memo(withGlobal<OwnProps>(
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 }),

View File

@ -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<OwnProps> = ({
message,
observeIntersection,
noAvatars,
shouldAutoLoad,
canAutoLoad,
isInSelectMode,
isSelected,
uploadProgress,
@ -72,7 +72,7 @@ const Photo: FC<OwnProps> = ({
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<OwnProps> = ({
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<OwnProps> = ({
height={height}
alt=""
/>
{shouldRenderSpinner && (
{shouldRenderSpinner && !shouldRenderDownloadButton && (
<div className={`media-loading ${spinnerClassNames}`}>
<ProgressSpinner progress={transferProgress} onClick={isUploading ? handleClick : undefined} />
</div>
)}
{!fullMediaData && !isLoadAllowed && (
<i className="icon-download" />
)}
{shouldRenderDownloadButton && <i className={buildClassName('icon-download', downloadButtonClassNames)} />}
{isTransferring && (
<span className="message-transfer-progress">{Math.round(transferProgress * 100)}%</span>
)}

View File

@ -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<OwnProps> = ({
message,
observeIntersection,
shouldAutoLoad,
shouldAutoPlay,
canAutoLoad,
lastSyncTime,
isDownloading,
}) => {
@ -71,7 +69,7 @@ const RoundVideo: FC<OwnProps> = ({
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<OwnProps> = ({
</div>
)}
{!mediaData && !isLoadAllowed && (
<i className="icon-large-play" />
<i className="icon-download" />
)}
<div className="message-media-duration">
{isActivated ? formatMediaDuration(playerRef.current!.currentTime) : formatMediaDuration(video.duration)}

View File

@ -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<OwnProps> = ({
message,
observeIntersection,
noAvatars,
shouldAutoLoad,
shouldAutoPlay,
canAutoLoad,
canAutoPlay,
uploadProgress,
lastSyncTime,
dimensions,
@ -70,9 +70,9 @@ const Video: FC<OwnProps> = ({
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<OwnProps> = ({
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<OwnProps> = ({
shouldRender: shouldRenderSpinner,
transitionClassNames: spinnerClassNames,
} = useShowTransition(isTransferring, undefined, wasLoadDisabled);
const {
shouldRender: shouldRenderPlayButton,
transitionClassNames: playButtonClassNames,
} = useShowTransition(isLoadAllowed && !isPlayAllowed && !shouldRenderSpinner);
const [playProgress, setPlayProgress] = useState<number>(0);
const handleTimeUpdate = useCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
@ -128,7 +132,7 @@ const Video: FC<OwnProps> = ({
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<OwnProps> = ({
<source src={fullMediaData} />
</video>
)}
{(isLoadAllowed && !isPlayAllowed && !shouldRenderSpinner) && (
<i className="icon-large-play" />
)}
{shouldRenderPlayButton && <i className={buildClassName('icon-large-play', playButtonClassNames)} />}
{shouldRenderSpinner && (
<div className={`media-loading ${spinnerClassNames}`}>
<div className={buildClassName('media-loading', spinnerClassNames)}>
<ProgressSpinner progress={transferProgress} onClick={handleClick} />
</div>
)}

View File

@ -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<OwnProps> = ({
message,
observeIntersection,
noAvatars,
shouldAutoLoad,
shouldAutoPlay,
canAutoLoad,
canAutoPlay,
inPreview,
lastSyncTime,
isDownloading = false,
@ -91,7 +91,7 @@ const WebPage: FC<OwnProps> = ({
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<OwnProps> = ({
message={message}
observeIntersection={observeIntersection!}
noAvatars={noAvatars}
shouldAutoLoad={shouldAutoLoad}
shouldAutoPlay={shouldAutoPlay}
canAutoLoad={canAutoLoad}
canAutoPlay={canAutoPlay}
lastSyncTime={lastSyncTime}
onClick={isMediaInteractive ? handleMediaClick : undefined}
onCancelUpload={onCancelMediaTransfer}

View File

@ -495,6 +495,14 @@
border-radius: 50%;
background: rgba(black, 0.25);
}
&.opacity-transition {
transition: opacity .15s ease;
&:not(.open) {
opacity: 0;
}
}
}
.icon-download {

View File

@ -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<OwnProps> = ({
step = 1,
label,
value,
renderValue,
disabled,
onChange,
}) => {
@ -46,7 +48,6 @@ const RangeSlider: FC<OwnProps> = ({
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<OwnProps> = ({
<div className="slider-top-row" dir={lang.isRtl ? 'rtl' : undefined}>
<span className="label" dir="auto">{label}</span>
{!options && (
<span className="value" dir="auto">{value}</span>
<span className="value" dir="auto">{renderValue ? renderValue(value) : value}</span>
)}
</div>
)}

View File

@ -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;

View File

@ -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;

View File

@ -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',

View File

@ -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) {

View File

@ -56,12 +56,21 @@ export interface ISettings extends NotifySettings, Record<string, any> {
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,

View File

@ -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;