Dark Theme: Auto-change when system theme changes
This commit is contained in:
parent
479106c9eb
commit
93fd353d1d
@ -7,7 +7,9 @@ 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 { 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 { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
@ -137,7 +139,7 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
setSettingOption({ theme: newTheme });
|
setSettingOption({ theme: newTheme });
|
||||||
setSettingOption({ shouldUseSystemTheme: false });
|
setSettingOption({ shouldUseSystemTheme: false });
|
||||||
switchTheme(newTheme, animationLevel > 0);
|
switchTheme(newTheme, animationLevel === ANIMATION_LEVEL_MAX);
|
||||||
}, [animationLevel, setSettingOption, theme]);
|
}, [animationLevel, setSettingOption, theme]);
|
||||||
|
|
||||||
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
const handleAnimationLevelChange = useCallback((e: React.SyntheticEvent<HTMLElement>) => {
|
||||||
|
|||||||
@ -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 {
|
import {
|
||||||
IS_ANDROID, IS_IOS, IS_SAFARI, IS_TOUCH_ENV,
|
IS_ANDROID, IS_IOS, IS_SAFARI, IS_TOUCH_ENV,
|
||||||
} from '../../../util/environment';
|
} from '../../../util/environment';
|
||||||
@ -7,6 +8,8 @@ import { setLanguage } from '../../../util/langProvider';
|
|||||||
import switchTheme from '../../../util/switchTheme';
|
import switchTheme from '../../../util/switchTheme';
|
||||||
import { selectTheme } from '../../selectors';
|
import { selectTheme } from '../../selectors';
|
||||||
|
|
||||||
|
subscribeToSystemThemeChange();
|
||||||
|
|
||||||
addReducer('init', (global) => {
|
addReducer('init', (global) => {
|
||||||
const { animationLevel, messageTextSize, language } = global.settings.byKey;
|
const { animationLevel, messageTextSize, language } = global.settings.byKey;
|
||||||
const theme = selectTheme(global);
|
const theme = selectTheme(global);
|
||||||
@ -17,7 +20,7 @@ addReducer('init', (global) => {
|
|||||||
document.body.classList.add('initial');
|
document.body.classList.add('initial');
|
||||||
document.body.classList.add(`animation-level-${animationLevel}`);
|
document.body.classList.add(`animation-level-${animationLevel}`);
|
||||||
document.body.classList.add(IS_TOUCH_ENV ? 'is-touch-env' : 'is-pointer-env');
|
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) {
|
if (IS_SAFARI) {
|
||||||
document.body.classList.add('is-safari');
|
document.body.classList.add('is-safari');
|
||||||
@ -64,3 +67,26 @@ addReducer('clearAuthError', (global) => {
|
|||||||
authError: undefined,
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { GlobalState } from '../../global/types';
|
import { GlobalState } from '../../global/types';
|
||||||
import { RightColumnContent } from '../../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 { selectCurrentMessageList, selectIsPollResultsOpen } from './messages';
|
||||||
import { selectCurrentTextSearch } from './localSearch';
|
import { selectCurrentTextSearch } from './localSearch';
|
||||||
import { selectCurrentStickerSearch, selectCurrentGifSearch } from './symbols';
|
import { selectCurrentStickerSearch, selectCurrentGifSearch } from './symbols';
|
||||||
@ -57,5 +57,5 @@ export function selectIsRightColumnShown(global: GlobalState) {
|
|||||||
export function selectTheme(global: GlobalState) {
|
export function selectTheme(global: GlobalState) {
|
||||||
const { theme, shouldUseSystemTheme } = global.settings.byKey;
|
const { theme, shouldUseSystemTheme } = global.settings.byKey;
|
||||||
|
|
||||||
return shouldUseSystemTheme ? SYSTEM_THEME : theme;
|
return shouldUseSystemTheme ? getSystemTheme() : theme;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export interface IAlbum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ThemeKey = 'light' | 'dark';
|
export type ThemeKey = 'light' | 'dark';
|
||||||
|
|
||||||
export interface IThemeSettings {
|
export interface IThemeSettings {
|
||||||
background?: string;
|
background?: string;
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
|||||||
@ -5,6 +5,10 @@ import {
|
|||||||
IS_TEST,
|
IS_TEST,
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
|
export * from './environmentWebp';
|
||||||
|
|
||||||
|
export * from './environmentSystemTheme';
|
||||||
|
|
||||||
export function getPlatform() {
|
export function getPlatform() {
|
||||||
const { userAgent, platform } = window.navigator;
|
const { userAgent, platform } = window.navigator;
|
||||||
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'];
|
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'];
|
||||||
@ -55,30 +59,3 @@ export const IS_CANVAS_FILTER_SUPPORTED = (
|
|||||||
export const DPR = window.devicePixelRatio || 1;
|
export const DPR = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
export const MASK_IMAGE_DISABLED = true;
|
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;
|
|
||||||
});
|
|
||||||
|
|||||||
20
src/util/environmentSystemTheme.ts
Normal file
20
src/util/environmentSystemTheme.ts
Normal 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);
|
||||||
|
}
|
||||||
22
src/util/environmentWebp.ts
Normal file
22
src/util/environmentWebp.ts
Normal 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;
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user