Dark Theme: Auto-change when system theme changes

This commit is contained in:
Alexander Zinchuk 2021-06-24 14:46:06 +03:00
parent 479106c9eb
commit 93fd353d1d
7 changed files with 81 additions and 33 deletions

View File

@ -7,7 +7,9 @@ import { GlobalActions } from '../../../global/types';
import { LeftColumnContent, ISettings } from '../../../types';
import { ApiChat } from '../../../api/types';
import { APP_NAME, APP_VERSION, FEEDBACK_URL } from '../../../config';
import {
ANIMATION_LEVEL_MAX, APP_NAME, APP_VERSION, FEEDBACK_URL,
} from '../../../config';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
import buildClassName from '../../../util/buildClassName';
import { pick } from '../../../util/iteratees';
@ -137,7 +139,7 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
setSettingOption({ theme: newTheme });
setSettingOption({ shouldUseSystemTheme: false });
switchTheme(newTheme, animationLevel > 0);
switchTheme(newTheme, animationLevel === ANIMATION_LEVEL_MAX);
}, [animationLevel, setSettingOption, theme]);
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {

View File

@ -1,5 +1,6 @@
import { addReducer } from '../../../lib/teact/teactn';
import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
import { ANIMATION_LEVEL_MAX } from '../../../config';
import {
IS_ANDROID, IS_IOS, IS_SAFARI, IS_TOUCH_ENV,
} from '../../../util/environment';
@ -7,6 +8,8 @@ import { setLanguage } from '../../../util/langProvider';
import switchTheme from '../../../util/switchTheme';
import { selectTheme } from '../../selectors';
subscribeToSystemThemeChange();
addReducer('init', (global) => {
const { animationLevel, messageTextSize, language } = global.settings.byKey;
const theme = selectTheme(global);
@ -17,7 +20,7 @@ addReducer('init', (global) => {
document.body.classList.add('initial');
document.body.classList.add(`animation-level-${animationLevel}`);
document.body.classList.add(IS_TOUCH_ENV ? 'is-touch-env' : 'is-pointer-env');
switchTheme(theme, animationLevel > 0);
switchTheme(theme, animationLevel === ANIMATION_LEVEL_MAX);
if (IS_SAFARI) {
document.body.classList.add('is-safari');
@ -64,3 +67,26 @@ addReducer('clearAuthError', (global) => {
authError: undefined,
};
});
function subscribeToSystemThemeChange() {
function handleSystemThemeChange() {
const currentThemeMatch = document.documentElement.className.match(/theme-(\w+)/);
const currentTheme = currentThemeMatch ? currentThemeMatch[1] : 'light';
const global = getGlobal();
const nextTheme = selectTheme(global);
const { animationLevel } = global.settings.byKey;
if (nextTheme !== currentTheme) {
switchTheme(nextTheme, animationLevel === ANIMATION_LEVEL_MAX);
// Force-update component containers
setGlobal({ ...global });
}
}
const mql = window.matchMedia('(prefers-color-scheme: dark)');
if (typeof mql.addEventListener === 'function') {
mql.addEventListener('change', handleSystemThemeChange);
} else if (typeof mql.addListener === 'function') {
mql.addListener(handleSystemThemeChange);
}
}

View File

@ -1,7 +1,7 @@
import { GlobalState } from '../../global/types';
import { RightColumnContent } from '../../types';
import { IS_SINGLE_COLUMN_LAYOUT, SYSTEM_THEME } from '../../util/environment';
import { getSystemTheme, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
import { selectCurrentMessageList, selectIsPollResultsOpen } from './messages';
import { selectCurrentTextSearch } from './localSearch';
import { selectCurrentStickerSearch, selectCurrentGifSearch } from './symbols';
@ -57,5 +57,5 @@ export function selectIsRightColumnShown(global: GlobalState) {
export function selectTheme(global: GlobalState) {
const { theme, shouldUseSystemTheme } = global.settings.byKey;
return shouldUseSystemTheme ? SYSTEM_THEME : theme;
return shouldUseSystemTheme ? getSystemTheme() : theme;
}

View File

@ -21,6 +21,7 @@ export interface IAlbum {
}
export type ThemeKey = 'light' | 'dark';
export interface IThemeSettings {
background?: string;
backgroundColor?: string;

View File

@ -5,6 +5,10 @@ import {
IS_TEST,
} from '../config';
export * from './environmentWebp';
export * from './environmentSystemTheme';
export function getPlatform() {
const { userAgent, platform } = window.navigator;
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'];
@ -55,30 +59,3 @@ export const IS_CANVAS_FILTER_SUPPORTED = (
export const DPR = window.devicePixelRatio || 1;
export const MASK_IMAGE_DISABLED = true;
export const SYSTEM_THEME = (
window && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
) ? 'dark' : 'light';
let isWebpSupportedCache: boolean | undefined;
export function isWebpSupported() {
return Boolean(isWebpSupportedCache);
}
function testWebp(): Promise<boolean> {
return new Promise((resolve) => {
const webp = new Image();
// eslint-disable-next-line max-len
webp.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
const handleLoadOrError = () => {
resolve(webp.height === 2);
};
webp.onload = handleLoadOrError;
webp.onerror = handleLoadOrError;
});
}
testWebp().then((hasWebp) => {
isWebpSupportedCache = hasWebp;
});

View File

@ -0,0 +1,20 @@
import { ThemeKey } from '../types';
let systemThemeCache: ThemeKey = (
window && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
) ? 'dark' : 'light';
export function getSystemTheme() {
return systemThemeCache;
}
function handleSystemThemeChange(e: MediaQueryListEventMap['change']) {
systemThemeCache = e.matches ? 'dark' : 'light';
}
const mql = window.matchMedia('(prefers-color-scheme: dark)');
if (typeof mql.addEventListener === 'function') {
mql.addEventListener('change', handleSystemThemeChange);
} else if (typeof mql.addListener === 'function') {
mql.addListener(handleSystemThemeChange);
}

View File

@ -0,0 +1,22 @@
let isWebpSupportedCache: boolean | undefined;
export function isWebpSupported() {
return Boolean(isWebpSupportedCache);
}
function testWebp(): Promise<boolean> {
return new Promise((resolve) => {
const webp = new Image();
// eslint-disable-next-line max-len
webp.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
const handleLoadOrError = () => {
resolve(webp.height === 2);
};
webp.onload = handleLoadOrError;
webp.onerror = handleLoadOrError;
});
}
testWebp().then((hasWebp) => {
isWebpSupportedCache = hasWebp;
});