mirror of
				https://github.com/vector-im/element-web.git
				synced 2025-11-04 10:11:03 +01:00 
			
		
		
		
	Merge pull request #3637 from matrix-org/dbkr/system_dark_mode
Get theme automatically from system setting
This commit is contained in:
		
						commit
						00241a8d0c
					
				@ -59,7 +59,7 @@ import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
 | 
			
		||||
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
 | 
			
		||||
import DMRoomMap from '../../utils/DMRoomMap';
 | 
			
		||||
import { countRoomsWithNotif } from '../../RoomNotifs';
 | 
			
		||||
import { setTheme } from "../../theme";
 | 
			
		||||
import { ThemeWatcher } from "../../theme";
 | 
			
		||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
 | 
			
		||||
import { defer } from "../../utils/promise";
 | 
			
		||||
 | 
			
		||||
@ -274,7 +274,8 @@ export default createReactClass({
 | 
			
		||||
 | 
			
		||||
    componentDidMount: function() {
 | 
			
		||||
        this.dispatcherRef = dis.register(this.onAction);
 | 
			
		||||
        this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onThemeChanged);
 | 
			
		||||
        this._themeWatcher = new ThemeWatcher();
 | 
			
		||||
        this._themeWatcher.start();
 | 
			
		||||
 | 
			
		||||
        this.focusComposer = false;
 | 
			
		||||
 | 
			
		||||
@ -361,7 +362,7 @@ export default createReactClass({
 | 
			
		||||
    componentWillUnmount: function() {
 | 
			
		||||
        Lifecycle.stopMatrixClient();
 | 
			
		||||
        dis.unregister(this.dispatcherRef);
 | 
			
		||||
        SettingsStore.unwatchSetting(this._themeWatchRef);
 | 
			
		||||
        this._themeWatcher.stop();
 | 
			
		||||
        window.removeEventListener("focus", this.onFocus);
 | 
			
		||||
        window.removeEventListener('resize', this.handleResize);
 | 
			
		||||
        this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
 | 
			
		||||
@ -384,13 +385,6 @@ export default createReactClass({
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _onThemeChanged: function(settingName, roomId, atLevel, newValue) {
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
            action: 'set_theme',
 | 
			
		||||
            value: newValue,
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    startPageChangeTimer() {
 | 
			
		||||
        // Tor doesn't support performance
 | 
			
		||||
        if (!performance || !performance.mark) return null;
 | 
			
		||||
@ -672,9 +666,6 @@ export default createReactClass({
 | 
			
		||||
                });
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case 'set_theme':
 | 
			
		||||
                setTheme(payload.value);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'on_logging_in':
 | 
			
		||||
                // We are now logging in, so set the state to reflect that
 | 
			
		||||
                // NB. This does not touch 'ready' since if our dispatches
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ import LanguageDropdown from "../../../elements/LanguageDropdown";
 | 
			
		||||
import AccessibleButton from "../../../elements/AccessibleButton";
 | 
			
		||||
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import {enumerateThemes} from "../../../../../theme";
 | 
			
		||||
import {enumerateThemes, ThemeWatcher} from "../../../../../theme";
 | 
			
		||||
import PlatformPeg from "../../../../../PlatformPeg";
 | 
			
		||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
 | 
			
		||||
import sdk from "../../../../..";
 | 
			
		||||
@ -50,6 +50,7 @@ export default class GeneralUserSettingsTab extends React.Component {
 | 
			
		||||
        this.state = {
 | 
			
		||||
            language: languageHandler.getCurrentLanguage(),
 | 
			
		||||
            theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"),
 | 
			
		||||
            useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"),
 | 
			
		||||
            haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()),
 | 
			
		||||
            serverSupportsSeparateAddAndBind: null,
 | 
			
		||||
            idServerHasUnsignedTerms: false,
 | 
			
		||||
@ -177,16 +178,25 @@ export default class GeneralUserSettingsTab extends React.Component {
 | 
			
		||||
        // so remember what the value was before we tried to set it so we can revert
 | 
			
		||||
        const oldTheme = SettingsStore.getValue('theme');
 | 
			
		||||
        SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => {
 | 
			
		||||
            dis.dispatch({action: 'set_theme', value: oldTheme});
 | 
			
		||||
            dis.dispatch({action: 'recheck_theme'});
 | 
			
		||||
            this.setState({theme: oldTheme});
 | 
			
		||||
        });
 | 
			
		||||
        this.setState({theme: newTheme});
 | 
			
		||||
        // The settings watcher doesn't fire until the echo comes back from the
 | 
			
		||||
        // server, so to make the theme change immediately we need to manually
 | 
			
		||||
        // do the dispatch now
 | 
			
		||||
        dis.dispatch({action: 'set_theme', value: newTheme});
 | 
			
		||||
        // XXX: The local echoed value appears to be unreliable, in particular
 | 
			
		||||
        // when settings custom themes(!) so adding forceTheme to override
 | 
			
		||||
        // the value from settings.
 | 
			
		||||
        dis.dispatch({action: 'recheck_theme', forceTheme: newTheme});
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _onUseSystemThemeChanged = (checked) => {
 | 
			
		||||
        this.setState({useSystemTheme: checked});
 | 
			
		||||
        dis.dispatch({action: 'recheck_theme'});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    _onPasswordChangeError = (err) => {
 | 
			
		||||
        // TODO: Figure out a design that doesn't involve replacing the current dialog
 | 
			
		||||
        let errMsg = err.error || "";
 | 
			
		||||
@ -297,11 +307,24 @@ export default class GeneralUserSettingsTab extends React.Component {
 | 
			
		||||
 | 
			
		||||
    _renderThemeSection() {
 | 
			
		||||
        const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
 | 
			
		||||
 | 
			
		||||
        const themeWatcher = new ThemeWatcher();
 | 
			
		||||
        let systemThemeSection;
 | 
			
		||||
        if (themeWatcher.isSystemThemeSupported()) {
 | 
			
		||||
            systemThemeSection = <div>
 | 
			
		||||
                <SettingsFlag name="use_system_theme" level={SettingLevel.DEVICE}
 | 
			
		||||
                    onChange={this._onUseSystemThemeChanged}
 | 
			
		||||
                />
 | 
			
		||||
            </div>;
 | 
			
		||||
        }
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
 | 
			
		||||
                <span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
 | 
			
		||||
                {systemThemeSection}
 | 
			
		||||
                <Field id="theme" label={_t("Theme")} element="select"
 | 
			
		||||
                       value={this.state.theme} onChange={this._onThemeChange}>
 | 
			
		||||
                       value={this.state.theme} onChange={this._onThemeChange}
 | 
			
		||||
                       disabled={this.state.useSystemTheme}
 | 
			
		||||
                >
 | 
			
		||||
                    {Object.entries(enumerateThemes()).map(([theme, text]) => {
 | 
			
		||||
                        return <option key={theme} value={theme}>{text}</option>;
 | 
			
		||||
                    })}
 | 
			
		||||
 | 
			
		||||
@ -364,6 +364,7 @@
 | 
			
		||||
    "Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
 | 
			
		||||
    "Mirror local video feed": "Mirror local video feed",
 | 
			
		||||
    "Enable Community Filter Panel": "Enable Community Filter Panel",
 | 
			
		||||
    "Match system dark mode setting": "Match system dark mode setting",
 | 
			
		||||
    "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls",
 | 
			
		||||
    "Send analytics data": "Send analytics data",
 | 
			
		||||
    "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device",
 | 
			
		||||
 | 
			
		||||
@ -281,6 +281,11 @@ export const SETTINGS = {
 | 
			
		||||
        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
 | 
			
		||||
        default: [],
 | 
			
		||||
    },
 | 
			
		||||
    "use_system_theme": {
 | 
			
		||||
        supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
 | 
			
		||||
        default: true,
 | 
			
		||||
        displayName: _td("Match system dark mode setting"),
 | 
			
		||||
    },
 | 
			
		||||
    "webRtcAllowPeerToPeer": {
 | 
			
		||||
        supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
 | 
			
		||||
        displayName: _td('Allow Peer-to-Peer for 1:1 calls'),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										70
									
								
								src/theme.js
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								src/theme.js
									
									
									
									
									
								
							@ -19,8 +19,75 @@ import {_t} from "./languageHandler";
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_THEME = "light";
 | 
			
		||||
import Tinter from "./Tinter";
 | 
			
		||||
import dis from "./dispatcher";
 | 
			
		||||
import SettingsStore from "./settings/SettingsStore";
 | 
			
		||||
 | 
			
		||||
export class ThemeWatcher {
 | 
			
		||||
    static _instance = null;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this._themeWatchRef = null;
 | 
			
		||||
        this._systemThemeWatchRef = null;
 | 
			
		||||
        this._dispatcherRef = null;
 | 
			
		||||
 | 
			
		||||
        // we have both here as each may either match or not match, so by having both
 | 
			
		||||
        // we can get the tristate of dark/light/unsupported
 | 
			
		||||
        this._preferDark = global.matchMedia("(prefers-color-scheme: dark)");
 | 
			
		||||
        this._preferLight = global.matchMedia("(prefers-color-scheme: light)");
 | 
			
		||||
 | 
			
		||||
        this._currentTheme = this.getEffectiveTheme();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    start() {
 | 
			
		||||
        this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onChange);
 | 
			
		||||
        this._systemThemeWatchRef = SettingsStore.watchSetting("use_system_theme", null, this._onChange);
 | 
			
		||||
        this._preferDark.addEventListener('change', this._onChange);
 | 
			
		||||
        this._preferLight.addEventListener('change', this._onChange);
 | 
			
		||||
        this._dispatcherRef = dis.register(this._onAction);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stop() {
 | 
			
		||||
        this._preferDark.removeEventListener('change', this._onChange);
 | 
			
		||||
        this._preferLight.removeEventListener('change', this._onChange);
 | 
			
		||||
        SettingsStore.unwatchSetting(this._systemThemeWatchRef);
 | 
			
		||||
        SettingsStore.unwatchSetting(this._themeWatchRef);
 | 
			
		||||
        dis.unregister(this._dispatcherRef);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onChange = () => {
 | 
			
		||||
        this.recheck();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onAction = (payload) => {
 | 
			
		||||
        if (payload.action === 'recheck_theme') {
 | 
			
		||||
            // XXX forceTheme
 | 
			
		||||
            this.recheck(payload.forceTheme);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // XXX: forceTheme param aded here as local echo appears to be unreliable
 | 
			
		||||
    // https://github.com/vector-im/riot-web/issues/11443
 | 
			
		||||
    recheck(forceTheme) {
 | 
			
		||||
        const oldTheme = this._currentTheme;
 | 
			
		||||
        this._currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme;
 | 
			
		||||
        if (oldTheme !== this._currentTheme) {
 | 
			
		||||
            setTheme(this._currentTheme);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getEffectiveTheme() {
 | 
			
		||||
        if (SettingsStore.getValue('use_system_theme')) {
 | 
			
		||||
            if (this._preferDark.matches) return 'dark';
 | 
			
		||||
            if (this._preferLight.matches) return 'light';
 | 
			
		||||
        }
 | 
			
		||||
        return SettingsStore.getValue('theme');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isSystemThemeSupported() {
 | 
			
		||||
        return this._preferDark.matches || this._preferLight.matches;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function enumerateThemes() {
 | 
			
		||||
    const BUILTIN_THEMES = {
 | 
			
		||||
        "light": _t("Light theme"),
 | 
			
		||||
@ -83,7 +150,8 @@ export function getBaseTheme(theme) {
 | 
			
		||||
 */
 | 
			
		||||
export function setTheme(theme) {
 | 
			
		||||
    if (!theme) {
 | 
			
		||||
        theme = SettingsStore.getValue("theme");
 | 
			
		||||
        const themeWatcher = new ThemeWatcher();
 | 
			
		||||
        theme = themeWatcher.getEffectiveTheme();
 | 
			
		||||
    }
 | 
			
		||||
    let stylesheetName = theme;
 | 
			
		||||
    if (theme.startsWith("custom-")) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user