Middle Column: Custom background should be memorized related to a theme (#1042)

This commit is contained in:
Alexander Zinchuk 2021-06-11 01:19:11 +03:00
parent b2242f7fe4
commit e5dc1f485b
15 changed files with 196 additions and 99 deletions

View File

@ -30,7 +30,7 @@ type OwnProps = {
type StateProps = Pick<GlobalState, 'uiReadyState'> & { type StateProps = Pick<GlobalState, 'uiReadyState'> & {
hasCustomBackground?: boolean; hasCustomBackground?: boolean;
isCustomBackgroundColor: boolean; hasCustomBackgroundColor: boolean;
isRightColumnShown?: boolean; isRightColumnShown?: boolean;
}; };
@ -80,7 +80,7 @@ const UiLoader: FC<OwnProps & StateProps & DispatchProps> = ({
page, page,
children, children,
hasCustomBackground, hasCustomBackground,
isCustomBackgroundColor, hasCustomBackgroundColor,
isRightColumnShown, isRightColumnShown,
setIsUiReady, setIsUiReady,
}) => { }) => {
@ -134,8 +134,8 @@ const UiLoader: FC<OwnProps & StateProps & DispatchProps> = ({
<div <div
className={buildClassName( className={buildClassName(
'middle', 'middle',
hasCustomBackground && !isCustomBackgroundColor && 'custom-bg-image', hasCustomBackground && 'custom-bg-image',
hasCustomBackground && isCustomBackgroundColor && 'custom-bg-color', hasCustomBackgroundColor && 'custom-bg-color',
isRightColumnShown && 'with-right-column', isRightColumnShown && 'with-right-column',
)} )}
/> />
@ -152,10 +152,13 @@ const UiLoader: FC<OwnProps & StateProps & DispatchProps> = ({
export default withGlobal<OwnProps>( export default withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { theme } = global.settings.byKey;
const { background, backgroundColor } = global.settings.themes[theme] || {};
return { return {
uiReadyState: global.uiReadyState, uiReadyState: global.uiReadyState,
hasCustomBackground: Boolean(global.settings.byKey.customBackground), hasCustomBackground: Boolean(background),
isCustomBackgroundColor: Boolean((global.settings.byKey.customBackground || '').match(/^#[a-f\d]{6,8}$/i)), hasCustomBackgroundColor: Boolean(backgroundColor),
isRightColumnShown: selectIsRightColumnShown(global), isRightColumnShown: selectIsRightColumnShown(global),
}; };
}, },

View File

@ -7,9 +7,7 @@ import { GlobalActions } from '../../../global/types';
import { LeftColumnContent, ISettings } from '../../../types'; import { LeftColumnContent, ISettings } from '../../../types';
import { ApiChat } from '../../../api/types'; import { ApiChat } from '../../../api/types';
import { import { APP_INFO, FEEDBACK_URL } from '../../../config';
APP_INFO, DEFAULT_PATTERN_COLOR, FEEDBACK_URL, DARK_THEME_BG_COLOR, DARK_THEME_PATTERN_COLOR,
} from '../../../config';
import { IS_MOBILE_SCREEN } from '../../../util/environment'; import { IS_MOBILE_SCREEN } from '../../../util/environment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
@ -128,13 +126,8 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLDivElement>) => { const handleDarkModeToggle = useCallback((e: React.SyntheticEvent<HTMLDivElement>) => {
e.stopPropagation(); e.stopPropagation();
const newTheme = theme === 'light' ? 'dark' : 'light'; const newTheme = theme === 'light' ? 'dark' : 'light';
const isNewThemeDark = newTheme === 'dark';
setSettingOption({ setSettingOption({ theme: newTheme });
theme: newTheme,
customBackground: isNewThemeDark ? DARK_THEME_BG_COLOR : undefined,
patternColor: isNewThemeDark ? DARK_THEME_PATTERN_COLOR : DEFAULT_PATTERN_COLOR,
});
switchTheme(newTheme, animationLevel > 0); switchTheme(newTheme, animationLevel > 0);
}, [animationLevel, setSettingOption, theme]); }, [animationLevel, setSettingOption, theme]);

View File

@ -1,17 +1,17 @@
import React, { import React, {
FC, memo, useEffect, useCallback, FC, memo, useEffect, useCallback, useRef,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { withGlobal } from '../../../lib/teact/teactn'; import { withGlobal } from '../../../lib/teact/teactn';
import { GlobalActions } from '../../../global/types'; import { GlobalActions } from '../../../global/types';
import { SettingsScreens, UPLOADING_WALLPAPER_SLUG } from '../../../types'; import { SettingsScreens, ThemeKey, UPLOADING_WALLPAPER_SLUG } from '../../../types';
import { ApiWallpaper } from '../../../api/types'; import { ApiWallpaper } from '../../../api/types';
import { DEFAULT_PATTERN_COLOR } from '../../../config'; import { DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../../../config';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
import { getAverageColor, getPatternColor } from '../../../util/colors'; import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -26,12 +26,15 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
customBackground?: string; background?: string;
isBackgroundBlurred?: boolean; isBlurred?: boolean;
loadedWallpapers?: ApiWallpaper[]; loadedWallpapers?: ApiWallpaper[];
theme: ThemeKey;
}; };
type DispatchProps = Pick<GlobalActions, 'setSettingOption' | 'loadWallpapers' | 'uploadWallpaper'>; type DispatchProps = Pick<GlobalActions, (
'loadWallpapers' | 'uploadWallpaper' | 'setThemeSettings'
)>;
const SUPPORTED_TYPES = 'image/jpeg'; const SUPPORTED_TYPES = 'image/jpeg';
@ -39,13 +42,16 @@ const runThrottled = throttle((cb) => cb(), 60000, true);
const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
onScreenSelect, onScreenSelect,
customBackground, background,
isBackgroundBlurred, isBlurred,
loadedWallpapers, loadedWallpapers,
setSettingOption, theme,
loadWallpapers, loadWallpapers,
uploadWallpaper, uploadWallpaper,
setThemeSettings,
}) => { }) => {
const themeRef = useRef<string>();
themeRef.current = theme;
// Due to the parent Transition, this component never gets unmounted, // Due to the parent Transition, this component never gets unmounted,
// that's why we use throttled API call on every update. // that's why we use throttled API call on every update.
useEffect(() => { useEffect(() => {
@ -71,23 +77,31 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
}, [onScreenSelect]); }, [onScreenSelect]);
const handleResetToDefault = useCallback(() => { const handleResetToDefault = useCallback(() => {
setSettingOption({ customBackground: undefined, patternColor: DEFAULT_PATTERN_COLOR }); setThemeSettings({
}, [setSettingOption]); theme,
background: undefined,
backgroundColor: undefined,
isBlurred: true,
patternColor: theme === 'dark' ? DARK_THEME_PATTERN_COLOR : DEFAULT_PATTERN_COLOR,
});
}, [setThemeSettings, theme]);
const handleWallPaperSelect = useCallback((slug: string) => { const handleWallPaperSelect = useCallback((slug: string) => {
setSettingOption({ customBackground: slug }); setThemeSettings({ theme: themeRef.current, background: slug });
const currentWallpaper = loadedWallpapers && loadedWallpapers.find((wallpaper) => wallpaper.slug === slug); const currentWallpaper = loadedWallpapers && loadedWallpapers.find((wallpaper) => wallpaper.slug === slug);
if (currentWallpaper && currentWallpaper.document.thumbnail) { if (currentWallpaper && currentWallpaper.document.thumbnail) {
getAverageColor(currentWallpaper.document.thumbnail.dataUri) getAverageColor(currentWallpaper.document.thumbnail.dataUri)
.then((color) => { .then((color) => {
setSettingOption({ patternColor: getPatternColor(color) }); const patternColor = getPatternColor(color);
const rgbColor = `#${rgb2hex(color)}`;
setThemeSettings({ theme: themeRef.current, backgroundColor: rgbColor, patternColor });
}); });
} }
}, [loadedWallpapers, setSettingOption]); }, [loadedWallpapers, setThemeSettings]);
const handleWallPaperBlurChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const handleWallPaperBlurChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSettingOption({ isBackgroundBlurred: e.target.checked }); setThemeSettings({ theme: themeRef.current, isBlurred: e.target.checked });
}, [setSettingOption]); }, [setThemeSettings]);
const lang = useLang(); const lang = useLang();
@ -119,7 +133,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
<Checkbox <Checkbox
label={lang('BackgroundBlurred')} label={lang('BackgroundBlurred')}
checked={Boolean(isBackgroundBlurred)} checked={Boolean(isBlurred)}
onChange={handleWallPaperBlurChange} onChange={handleWallPaperBlurChange}
/> />
</div> </div>
@ -129,7 +143,8 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
{loadedWallpapers.map((wallpaper) => ( {loadedWallpapers.map((wallpaper) => (
<WallpaperTile <WallpaperTile
wallpaper={wallpaper} wallpaper={wallpaper}
isSelected={customBackground === wallpaper.slug} theme={theme}
isSelected={background === wallpaper.slug}
onClick={handleWallPaperSelect} onClick={handleWallPaperSelect}
/> />
))} ))}
@ -143,16 +158,18 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { isBackgroundBlurred, customBackground } = global.settings.byKey; const { theme } = global.settings.byKey;
const { background, isBlurred } = global.settings.themes[theme] || {};
const { loadedWallpapers } = global.settings; const { loadedWallpapers } = global.settings;
return { return {
customBackground, background,
isBackgroundBlurred, isBlurred,
loadedWallpapers, loadedWallpapers,
theme,
}; };
}, },
(setGlobal, actions): DispatchProps => pick(actions, [ (setGlobal, actions): DispatchProps => pick(actions, [
'setSettingOption', 'loadWallpapers', 'uploadWallpaper', 'loadWallpapers', 'uploadWallpaper', 'setThemeSettings',
]), ]),
)(SettingsGeneralBackground)); )(SettingsGeneralBackground));

View File

@ -5,7 +5,7 @@ import React, {
import { withGlobal } from '../../../lib/teact/teactn'; import { withGlobal } from '../../../lib/teact/teactn';
import { GlobalActions } from '../../../global/types'; import { GlobalActions } from '../../../global/types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens, ThemeKey } from '../../../types';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { import {
@ -24,10 +24,11 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
customBackground?: string; backgroundColor?: string;
theme: ThemeKey;
}; };
type DispatchProps = Pick<GlobalActions, 'setSettingOption'>; type DispatchProps = Pick<GlobalActions, 'setThemeSettings'>;
interface CanvasRects { interface CanvasRects {
colorRect: { colorRect: {
@ -49,9 +50,12 @@ const PREDEFINED_COLORS = [
]; ];
const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
customBackground, theme,
setSettingOption, backgroundColor,
setThemeSettings,
}) => { }) => {
const themeRef = useRef<string>();
themeRef.current = theme;
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -60,7 +64,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
const huePickerRef = useRef<HTMLDivElement>(null); const huePickerRef = useRef<HTMLDivElement>(null);
const isFirstRunRef = useRef(true); const isFirstRunRef = useRef(true);
const [hsb, setHsb] = useState(getInitialHsb(customBackground)); const [hsb, setHsb] = useState(getInitialHsb(backgroundColor));
// Cache for drag handlers // Cache for drag handlers
const hsbRef = useRef(hsb); const hsbRef = useRef(hsb);
useEffect(() => { useEffect(() => {
@ -139,13 +143,16 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
setHexInput(color); setHexInput(color);
if (!isFirstRunRef.current) { if (!isFirstRunRef.current) {
setSettingOption({ const patternColor = getPatternColor(rgb);
customBackground: color, setThemeSettings({
patternColor: getPatternColor(rgb), theme: themeRef.current,
background: undefined,
backgroundColor: color,
patternColor,
}); });
} }
isFirstRunRef.current = false; isFirstRunRef.current = false;
}, [hsb, setSettingOption]); }, [hsb, setThemeSettings]);
// Redraw color picker when hue changes // Redraw color picker when hue changes
useEffect(() => { useEffect(() => {
@ -226,9 +233,9 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
); );
}; };
function getInitialHsb(customBackground?: string) { function getInitialHsb(backgroundColor?: string) {
return customBackground && customBackground.startsWith('#') return backgroundColor && backgroundColor.startsWith('#')
? rgb2hsb(hex2rgb(customBackground.replace('#', ''))) ? rgb2hsb(hex2rgb(backgroundColor.replace('#', '')))
: DEFAULT_HSB; : DEFAULT_HSB;
} }
@ -329,9 +336,12 @@ function drawHue(canvas: HTMLCanvasElement) {
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { theme } = global.settings.byKey;
const { backgroundColor } = global.settings.themes[theme] || {};
return { return {
customBackground: global.settings.byKey.customBackground, backgroundColor,
theme,
}; };
}, },
(setGlobal, actions): DispatchProps => pick(actions, ['setSettingOption']), (setGlobal, actions): DispatchProps => pick(actions, ['setThemeSettings']),
)(SettingsGeneralBackground)); )(SettingsGeneralBackground));

View File

@ -1,8 +1,8 @@
import React, { import React, {
FC, memo, useCallback, useEffect, useState, FC, memo, useCallback, useEffect, useState, useRef,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { ApiWallpaper } from '../../../api/types'; import { ApiWallpaper } from '../../../api/types';
import { UPLOADING_WALLPAPER_SLUG } from '../../../types'; import { ThemeKey, UPLOADING_WALLPAPER_SLUG } from '../../../types';
import { CUSTOM_BG_CACHE_NAME } from '../../../config'; import { CUSTOM_BG_CACHE_NAME } from '../../../config';
import * as cacheApi from '../../../util/cacheApi'; import * as cacheApi from '../../../util/cacheApi';
@ -21,6 +21,7 @@ import './WallpaperTile.scss';
type OwnProps = { type OwnProps = {
wallpaper: ApiWallpaper; wallpaper: ApiWallpaper;
theme: ThemeKey;
isSelected: boolean; isSelected: boolean;
onClick: (slug: string) => void; onClick: (slug: string) => void;
}; };
@ -29,11 +30,11 @@ const ANIMATION_DURATION = 300;
const WallpaperTile: FC<OwnProps> = ({ const WallpaperTile: FC<OwnProps> = ({
wallpaper, wallpaper,
theme,
isSelected, isSelected,
onClick, onClick,
}) => { }) => {
const { slug, document } = wallpaper; const { slug, document } = wallpaper;
const localMediaHash = `wallpaper${document.id!}`; const localMediaHash = `wallpaper${document.id!}`;
const localBlobUrl = document.previewBlobUrl; const localBlobUrl = document.previewBlobUrl;
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`); const previewBlobUrl = useMedia(`${localMediaHash}?size=m`);
@ -56,11 +57,14 @@ const WallpaperTile: FC<OwnProps> = ({
wasDownloadDisabled, wasDownloadDisabled,
'slow', 'slow',
); );
// To prevent triggering of the effect for useCallback
const cacheKeyRef = useRef<string>();
cacheKeyRef.current = theme;
const handleSelect = useCallback(() => { const handleSelect = useCallback(() => {
(async () => { (async () => {
const blob = await fetchBlob(fullMedia!); const blob = await fetchBlob(fullMedia!);
await cacheApi.save(CUSTOM_BG_CACHE_NAME, CUSTOM_BG_CACHE_NAME, blob); await cacheApi.save(CUSTOM_BG_CACHE_NAME, cacheKeyRef.current!, blob);
onClick(slug); onClick(slug);
})(); })();
}, [fullMedia, onClick, slug]); }, [fullMedia, onClick, slug]);

View File

@ -16,11 +16,18 @@
left: 0; left: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
background-color: #A2AF8E; background-color: var(--theme-background-color);
} }
&::after { &::after {
background-image: url('../../assets/chat-bg.jpg'); .theme-light & {
background-image: url('../../assets/chat-bg.jpg');
@media (max-width: 600px) {
background-image: url('../../assets/chat-bg-mobile.jpg');
}
}
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: cover;
@ -32,10 +39,15 @@
body.animation-level-0 & { body.animation-level-0 & {
transition: none; transition: none;
} }
}
@media (max-width: 600px) { .custom-bg-color > &::before {
background-image: url('../../assets/chat-bg-mobile.jpg'); filter: blur(0);
} transform: scale(1.1);
}
.custom-bg-color:not(.custom-bg-image) > &::after {
opacity: 0;
} }
.custom-bg-image > &::after { .custom-bg-image > &::after {
@ -44,20 +56,10 @@
transform: scale(1.1); transform: scale(1.1);
} }
.custom-bg-color > &::before {
background-color: var(--custom-background) !important;
filter: blur(0);
transform: scale(1.1);
}
.custom-bg-image.blurred > &::after { .custom-bg-image.blurred > &::after {
filter: blur(12px); filter: blur(12px);
} }
.custom-bg-color > &::after {
opacity: 0;
}
@media screen and (min-width: 1276px) { @media screen and (min-width: 1276px) {
body.animation-level-2 &::before, body.animation-level-2 &::before,
body.animation-level-2 &::after { body.animation-level-2 &::after {

View File

@ -5,6 +5,7 @@ import { withGlobal } from '../../lib/teact/teactn';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
import { GlobalActions, MessageListType } from '../../global/types'; import { GlobalActions, MessageListType } from '../../global/types';
import { ThemeKey } from '../../types';
import { import {
MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN,
@ -15,6 +16,8 @@ import {
CONTENT_TYPES_FOR_QUICK_UPLOAD, CONTENT_TYPES_FOR_QUICK_UPLOAD,
ANIMATION_LEVEL_MAX, ANIMATION_LEVEL_MAX,
ANIMATION_END_DELAY, ANIMATION_END_DELAY,
DARK_THEME_BG_COLOR,
LIGHT_THEME_BG_COLOR,
} from '../../config'; } from '../../config';
import { IS_MOBILE_SCREEN, IS_TOUCH_ENV, MASK_IMAGE_DISABLED } from '../../util/environment'; import { IS_MOBILE_SCREEN, IS_TOUCH_ENV, MASK_IMAGE_DISABLED } from '../../util/environment';
import { DropAreaState } from './composer/DropArea'; import { DropAreaState } from './composer/DropArea';
@ -59,9 +62,10 @@ type StateProps = {
messageSendingRestrictionReason?: string; messageSendingRestrictionReason?: string;
hasPinnedOrAudioMessage?: boolean; hasPinnedOrAudioMessage?: boolean;
pinnedMessagesCount?: number; pinnedMessagesCount?: number;
theme: ThemeKey;
customBackground?: string; customBackground?: string;
backgroundColor?: string;
patternColor?: string; patternColor?: string;
isCustomBackgroundColor?: boolean;
isRightColumnShown?: boolean; isRightColumnShown?: boolean;
isBackgroundBlurred?: boolean; isBackgroundBlurred?: boolean;
isMobileSearchActive?: boolean; isMobileSearchActive?: boolean;
@ -88,8 +92,9 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
hasPinnedOrAudioMessage, hasPinnedOrAudioMessage,
pinnedMessagesCount, pinnedMessagesCount,
customBackground, customBackground,
theme,
backgroundColor,
patternColor, patternColor,
isCustomBackgroundColor,
isRightColumnShown, isRightColumnShown,
isBackgroundBlurred, isBackgroundBlurred,
isMobileSearchActive, isMobileSearchActive,
@ -173,12 +178,12 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
openChat({ id: chatId }); openChat({ id: chatId });
}, [unpinAllMessages, openChat, closeUnpinModal, chatId]); }, [unpinAllMessages, openChat, closeUnpinModal, chatId]);
const customBackgroundValue = useCustomBackground(customBackground); const customBackgroundValue = useCustomBackground(theme, customBackground);
const className = buildClassName( const className = buildClassName(
renderingHasTools && 'has-header-tools', renderingHasTools && 'has-header-tools',
customBackground && !isCustomBackgroundColor && 'custom-bg-image', customBackground && 'custom-bg-image',
customBackground && isCustomBackgroundColor && 'custom-bg-color', backgroundColor && 'custom-bg-color',
customBackground && isBackgroundBlurred && 'blurred', customBackground && isBackgroundBlurred && 'blurred',
MASK_IMAGE_DISABLED ? 'mask-image-disabled' : 'mask-image-enabled', MASK_IMAGE_DISABLED ? 'mask-image-disabled' : 'mask-image-enabled',
); );
@ -219,6 +224,8 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
--composer-translate-x: ${composerTranslateX}px; --composer-translate-x: ${composerTranslateX}px;
--toolbar-translate-x: ${toolbarTranslateX}px; --toolbar-translate-x: ${toolbarTranslateX}px;
--pattern-color: ${patternColor}; --pattern-color: ${patternColor};
--theme-background-color:
${backgroundColor || (theme === 'dark' ? DARK_THEME_BG_COLOR : LIGHT_THEME_BG_COLOR)};
`} `}
> >
<div <div
@ -318,16 +325,19 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => { (global): StateProps => {
const { isBackgroundBlurred, customBackground, patternColor } = global.settings.byKey; const { theme } = global.settings.byKey;
const {
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
} = global.settings.themes[theme] || {};
const isCustomBackgroundColor = Boolean((customBackground || '').match(/^#[a-f\d]{6,8}$/i));
const currentMessageList = selectCurrentMessageList(global); const currentMessageList = selectCurrentMessageList(global);
const { chats: { listIds } } = global; const { chats: { listIds } } = global;
const state: StateProps = { const state: StateProps = {
theme,
customBackground, customBackground,
backgroundColor,
patternColor, patternColor,
isCustomBackgroundColor,
isRightColumnShown: selectIsRightColumnShown(global), isRightColumnShown: selectIsRightColumnShown(global),
isBackgroundBlurred, isBackgroundBlurred,
isMobileSearchActive: Boolean(IS_MOBILE_SCREEN && selectCurrentTextSearch(global)), isMobileSearchActive: Boolean(IS_MOBILE_SCREEN && selectCurrentTextSearch(global)),

View File

@ -127,6 +127,7 @@ export const DEFAULT_LANG_PACK = 'android';
export const LANG_PACKS = ['android', 'ios', 'tdesktop', 'macos']; export const LANG_PACKS = ['android', 'ios', 'tdesktop', 'macos'];
export const TIPS_USERNAME = 'TelegramTips'; export const TIPS_USERNAME = 'TelegramTips';
export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time'; export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
export const LIGHT_THEME_BG_COLOR = '#A2AF8E';
export const DARK_THEME_BG_COLOR = '#0F0F0F'; export const DARK_THEME_BG_COLOR = '#0F0F0F';
export const DARK_THEME_PATTERN_COLOR = '#0a0a0a8c'; export const DARK_THEME_PATTERN_COLOR = '#0a0a0a8c';
export const DEFAULT_PATTERN_COLOR = 'rgba(90, 110, 70, 0.6)'; export const DEFAULT_PATTERN_COLOR = 'rgba(90, 110, 70, 0.6)';

View File

@ -77,6 +77,10 @@ function readCache(initialState: GlobalState) {
...initialState.settings.byKey, ...initialState.settings.byKey,
...cached.settings.byKey, ...cached.settings.byKey,
}; };
cached.settings.themes = {
...initialState.settings.themes,
...cached.settings.themes,
};
} }
return { return {
@ -204,10 +208,11 @@ function reduceMessages(global: GlobalState): GlobalState['messages'] {
} }
function reduceSettings(global: GlobalState): GlobalState['settings'] { function reduceSettings(global: GlobalState): GlobalState['settings'] {
const { byKey } = global.settings; const { byKey, themes } = global.settings;
return { return {
byKey, byKey,
themes,
privacy: {}, privacy: {},
notifyExceptions: {}, notifyExceptions: {},
}; };

View File

@ -1,6 +1,8 @@
import { GlobalState } from './types'; import { GlobalState } from './types';
import { ANIMATION_LEVEL_DEFAULT, DEFAULT_MESSAGE_TEXT_SIZE_PX, DEFAULT_PATTERN_COLOR } from '../config'; import {
ANIMATION_LEVEL_DEFAULT, DARK_THEME_PATTERN_COLOR, DEFAULT_MESSAGE_TEXT_SIZE_PX, DEFAULT_PATTERN_COLOR,
} from '../config';
export const INITIAL_STATE: GlobalState = { export const INITIAL_STATE: GlobalState = {
isLeftColumnShown: true, isLeftColumnShown: true,
@ -100,8 +102,6 @@ export const INITIAL_STATE: GlobalState = {
settings: { settings: {
byKey: { byKey: {
messageTextSize: DEFAULT_MESSAGE_TEXT_SIZE_PX, messageTextSize: DEFAULT_MESSAGE_TEXT_SIZE_PX,
isBackgroundBlurred: true,
patternColor: DEFAULT_PATTERN_COLOR,
animationLevel: ANIMATION_LEVEL_DEFAULT, animationLevel: ANIMATION_LEVEL_DEFAULT,
messageSendKeyCombo: 'enter', messageSendKeyCombo: 'enter',
theme: 'light', theme: 'light',
@ -115,6 +115,16 @@ export const INITIAL_STATE: GlobalState = {
shouldLoopStickers: true, shouldLoopStickers: true,
language: 'en', language: 'en',
}, },
themes: {
light: {
isBlurred: true,
patternColor: DEFAULT_PATTERN_COLOR,
},
dark: {
isBlurred: true,
patternColor: DARK_THEME_PATTERN_COLOR,
},
},
privacy: {}, privacy: {},
notifyExceptions: {}, notifyExceptions: {},
}, },

View File

@ -34,6 +34,8 @@ import {
Receipt, Receipt,
ApiPrivacyKey, ApiPrivacyKey,
ApiPrivacySettings, ApiPrivacySettings,
ThemeKey,
IThemeSettings,
NotifyException, NotifyException,
} from '../types'; } from '../types';
@ -365,6 +367,7 @@ export type GlobalState = {
settings: { settings: {
byKey: ISettings; byKey: ISettings;
loadedWallpapers?: ApiWallpaper[]; loadedWallpapers?: ApiWallpaper[];
themes: Partial<Record<ThemeKey, IThemeSettings>>;
privacy: Partial<Record<ApiPrivacyKey, ApiPrivacySettings>>; privacy: Partial<Record<ApiPrivacyKey, ApiPrivacySettings>>;
notifyExceptions?: Record<number, NotifyException>; notifyExceptions?: Record<number, NotifyException>;
}; };
@ -438,7 +441,7 @@ export type ActionTypes = (
'loadAuthorizations' | 'terminateAuthorization' | 'terminateAllAuthorizations' | 'loadAuthorizations' | 'terminateAuthorization' | 'terminateAllAuthorizations' |
'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings' | 'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings' |
'loadLanguages' | 'loadPrivacySettings' | 'setPrivacyVisibility' | 'setPrivacySettings' | 'loadLanguages' | 'loadPrivacySettings' | 'setPrivacyVisibility' | 'setPrivacySettings' |
'loadNotificationExceptions' | 'loadNotificationExceptions' | 'setThemeSettings' |
// Stickers & GIFs // Stickers & GIFs
'loadStickerSets' | 'loadAddedStickers' | 'loadRecentStickers' | 'loadFavoriteStickers' | 'loadFeaturedStickers' | 'loadStickerSets' | 'loadAddedStickers' | 'loadRecentStickers' | 'loadFavoriteStickers' | 'loadFeaturedStickers' |
'loadStickers' | 'setStickerSearchQuery' | 'loadSavedGifs' | 'setGifSearchQuery' | 'searchMoreGifs' | 'loadStickers' | 'setStickerSearchQuery' | 'loadSavedGifs' | 'setGifSearchQuery' | 'searchMoreGifs' |

View File

@ -1,8 +1,12 @@
import { CUSTOM_BG_CACHE_NAME } from '../config';
import * as cacheApi from '../util/cacheApi';
import { useEffect, useState } from '../lib/teact/teact'; import { useEffect, useState } from '../lib/teact/teact';
export default (settingValue?: string) => { import { ThemeKey } from '../types';
import { CUSTOM_BG_CACHE_NAME } from '../config';
import * as cacheApi from '../util/cacheApi';
import { preloadImage } from '../util/files';
export default (theme: ThemeKey, settingValue?: string) => {
const [value, setValue] = useState(settingValue); const [value, setValue] = useState(settingValue);
useEffect(() => { useEffect(() => {
@ -13,12 +17,16 @@ export default (settingValue?: string) => {
if (settingValue.startsWith('#')) { if (settingValue.startsWith('#')) {
setValue(settingValue); setValue(settingValue);
} else { } else {
cacheApi.fetch(CUSTOM_BG_CACHE_NAME, CUSTOM_BG_CACHE_NAME, cacheApi.Type.Blob) cacheApi.fetch(CUSTOM_BG_CACHE_NAME, theme, cacheApi.Type.Blob)
.then((blob) => { .then((blob) => {
setValue(`url(${URL.createObjectURL(blob)}`); const url = URL.createObjectURL(blob);
preloadImage(url)
.then(() => {
setValue(`url(${url})`);
});
}); });
} }
}, [settingValue]); }, [settingValue, theme]);
return value; return settingValue ? value : undefined;
}; };

View File

@ -1,7 +1,13 @@
import { addReducer } from '../../../lib/teact/teactn'; import { addReducer } from '../../../lib/teact/teactn';
import { ISettings } from '../../../types'; import { ISettings, IThemeSettings, ThemeKey } from '../../../types';
import { replaceSettings } from '../../reducers'; import { replaceSettings, replaceThemeSettings } from '../../reducers';
addReducer('setSettingOption', (global, actions, payload?: Partial<ISettings>) => { addReducer('setSettingOption', (global, actions, payload?: Partial<ISettings>) => {
return replaceSettings(global, payload); return replaceSettings(global, payload);
}); });
addReducer('setThemeSettings', (global, actions, payload: { theme: ThemeKey } & Partial<IThemeSettings>) => {
const { theme, ...settings } = payload;
return replaceThemeSettings(global, theme, settings);
});

View File

@ -1,5 +1,7 @@
import { GlobalState } from '../../global/types'; import { GlobalState } from '../../global/types';
import { ISettings, NotifyException } from '../../types'; import {
ISettings, IThemeSettings, ThemeKey, NotifyException,
} from '../../types';
export function replaceSettings(global: GlobalState, newSettings?: Partial<ISettings>): GlobalState { export function replaceSettings(global: GlobalState, newSettings?: Partial<ISettings>): GlobalState {
return { return {
@ -14,6 +16,24 @@ export function replaceSettings(global: GlobalState, newSettings?: Partial<ISett
}; };
} }
export function replaceThemeSettings(
global: GlobalState, theme: ThemeKey, newSettings?: Partial<IThemeSettings>,
): GlobalState {
return {
...global,
settings: {
...global.settings,
themes: {
...global.settings.themes,
[theme]: {
...(global.settings.themes[theme] || {}),
...newSettings,
},
},
},
};
}
export function addNotifyException( export function addNotifyException(
global: GlobalState, id: number, notifyException: NotifyException, global: GlobalState, id: number, notifyException: NotifyException,
): GlobalState { ): GlobalState {

View File

@ -20,6 +20,14 @@ export interface IAlbum {
mainMessage: ApiMessage; mainMessage: ApiMessage;
} }
export type ThemeKey = 'light' | 'dark';
export interface IThemeSettings {
background?: string;
backgroundColor?: string;
patternColor?: string;
isBlurred?: boolean;
}
export type NotifySettings = { export type NotifySettings = {
hasPrivateChatsNotifications?: boolean; hasPrivateChatsNotifications?: boolean;
hasPrivateChatsMessagePreview?: boolean; hasPrivateChatsMessagePreview?: boolean;
@ -31,13 +39,10 @@ export type NotifySettings = {
}; };
export interface ISettings extends NotifySettings, Record<string, any> { export interface ISettings extends NotifySettings, Record<string, any> {
theme: ThemeKey;
messageTextSize: number; messageTextSize: number;
customBackground?: string;
patternColor?: string;
isBackgroundBlurred?: boolean;
animationLevel: 0 | 1 | 2; animationLevel: 0 | 1 | 2;
messageSendKeyCombo: 'enter' | 'ctrl-enter'; messageSendKeyCombo: 'enter' | 'ctrl-enter';
theme: 'light' | 'dark';
shouldAutoDownloadMediaFromContacts: boolean; shouldAutoDownloadMediaFromContacts: boolean;
shouldAutoDownloadMediaInPrivateChats: boolean; shouldAutoDownloadMediaInPrivateChats: boolean;
shouldAutoDownloadMediaInGroups: boolean; shouldAutoDownloadMediaInGroups: boolean;