Respect the system high contrast setting when using system theme (#7043)

* Respect the system high contrast setting when using system theme

* Restore correct behaviour of getEffectiveTheme
pull/21833/head
Andy Balaam 2021-10-28 13:01:50 +01:00 committed by GitHub
parent 76254977d6
commit 68b64564c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 8 deletions

View File

@ -2,6 +2,7 @@
$accent: #268075; $accent: #268075;
$alert: #D62C25; $alert: #D62C25;
$links: #0A6ECA; $links: #0A6ECA;
$primary-content: #17191C;
$secondary-content: #5E6266; $secondary-content: #5E6266;
$tertiary-content: $secondary-content; $tertiary-content: $secondary-content;
$quaternary-content: $secondary-content; $quaternary-content: $secondary-content;
@ -106,3 +107,11 @@ $roomtopic-color: $secondary-content;
.mx_FontScalingPanel_fontSlider { .mx_FontScalingPanel_fontSlider {
background-color: $roomlist-button-bg-color !important; background-color: $roomlist-button-bg-color !important;
} }
.mx_ThemeChoicePanel > .mx_ThemeSelectors > .mx_RadioButton input[type="radio"]:disabled + div {
border-color: $primary-content;
}
.mx_ThemeChoicePanel > .mx_ThemeSelectors > .mx_RadioButton.mx_RadioButton_disabled {
color: $primary-content;
}

View File

@ -32,7 +32,7 @@ import FeedbackDialog from "../views/dialogs/FeedbackDialog";
import Modal from "../../Modal"; import Modal from "../../Modal";
import LogoutDialog from "../views/dialogs/LogoutDialog"; import LogoutDialog from "../views/dialogs/LogoutDialog";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { getCustomTheme } from "../../theme"; import { findHighContrastTheme, getCustomTheme, isHighContrastTheme } from "../../theme";
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import SdkConfig from "../../SdkConfig"; import SdkConfig from "../../SdkConfig";
import { getHomePageUrl } from "../../utils/pages"; import { getHomePageUrl } from "../../utils/pages";
@ -69,6 +69,7 @@ type PartialDOMRect = Pick<DOMRect, "width" | "left" | "top" | "height">;
interface IState { interface IState {
contextMenuPosition: PartialDOMRect; contextMenuPosition: PartialDOMRect;
isDarkTheme: boolean; isDarkTheme: boolean;
isHighContrast: boolean;
selectedSpace?: Room; selectedSpace?: Room;
pendingRoomJoin: Set<string>; pendingRoomJoin: Set<string>;
} }
@ -87,6 +88,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
this.state = { this.state = {
contextMenuPosition: null, contextMenuPosition: null,
isDarkTheme: this.isUserOnDarkTheme(), isDarkTheme: this.isUserOnDarkTheme(),
isHighContrast: this.isUserOnHighContrastTheme(),
pendingRoomJoin: new Set<string>(), pendingRoomJoin: new Set<string>(),
}; };
@ -142,6 +144,18 @@ export default class UserMenu extends React.Component<IProps, IState> {
} }
} }
private isUserOnHighContrastTheme(): boolean {
if (SettingsStore.getValue("use_system_theme")) {
return window.matchMedia("(prefers-contrast: more)").matches;
} else {
const theme = SettingsStore.getValue("theme");
if (theme.startsWith("custom-")) {
return false;
}
return isHighContrastTheme(theme);
}
}
private onProfileUpdate = async () => { private onProfileUpdate = async () => {
// the store triggered an update, so force a layout update. We don't // the store triggered an update, so force a layout update. We don't
// have any state to store here for that to magically happen. // have any state to store here for that to magically happen.
@ -153,7 +167,11 @@ export default class UserMenu extends React.Component<IProps, IState> {
}; };
private onThemeChanged = () => { private onThemeChanged = () => {
this.setState({ isDarkTheme: this.isUserOnDarkTheme() }); this.setState(
{
isDarkTheme: this.isUserOnDarkTheme(),
isHighContrast: this.isUserOnHighContrastTheme(),
});
}; };
private onAction = (ev: ActionPayload) => { private onAction = (ev: ActionPayload) => {
@ -221,7 +239,13 @@ export default class UserMenu extends React.Component<IProps, IState> {
// Disable system theme matching if the user hits this button // Disable system theme matching if the user hits this button
SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false); SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false);
const newTheme = this.state.isDarkTheme ? "light" : "dark"; let newTheme = this.state.isDarkTheme ? "light" : "dark";
if (this.state.isHighContrast) {
const hcTheme = findHighContrastTheme(newTheme);
if (hcTheme) {
newTheme = hcTheme;
}
}
SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab
}; };

View File

@ -19,7 +19,7 @@ import SettingsStore from '../SettingsStore';
import dis from '../../dispatcher/dispatcher'; import dis from '../../dispatcher/dispatcher';
import { Action } from '../../dispatcher/actions'; import { Action } from '../../dispatcher/actions';
import ThemeController from "../controllers/ThemeController"; import ThemeController from "../controllers/ThemeController";
import { setTheme } from "../../theme"; import { findHighContrastTheme, setTheme } from "../../theme";
import { ActionPayload } from '../../dispatcher/payloads'; import { ActionPayload } from '../../dispatcher/payloads';
import { SettingLevel } from "../SettingLevel"; import { SettingLevel } from "../SettingLevel";
@ -32,6 +32,7 @@ export default class ThemeWatcher {
private preferDark: MediaQueryList; private preferDark: MediaQueryList;
private preferLight: MediaQueryList; private preferLight: MediaQueryList;
private preferHighContrast: MediaQueryList;
private currentTheme: string; private currentTheme: string;
@ -44,6 +45,7 @@ export default class ThemeWatcher {
// we can get the tristate of dark/light/unsupported // we can get the tristate of dark/light/unsupported
this.preferDark = (<any>global).matchMedia("(prefers-color-scheme: dark)"); this.preferDark = (<any>global).matchMedia("(prefers-color-scheme: dark)");
this.preferLight = (<any>global).matchMedia("(prefers-color-scheme: light)"); this.preferLight = (<any>global).matchMedia("(prefers-color-scheme: light)");
this.preferHighContrast = (<any>global).matchMedia("(prefers-contrast: more)");
this.currentTheme = this.getEffectiveTheme(); this.currentTheme = this.getEffectiveTheme();
} }
@ -54,6 +56,7 @@ export default class ThemeWatcher {
if (this.preferDark.addEventListener) { if (this.preferDark.addEventListener) {
this.preferDark.addEventListener('change', this.onChange); this.preferDark.addEventListener('change', this.onChange);
this.preferLight.addEventListener('change', this.onChange); this.preferLight.addEventListener('change', this.onChange);
this.preferHighContrast.addEventListener('change', this.onChange);
} }
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
} }
@ -62,6 +65,7 @@ export default class ThemeWatcher {
if (this.preferDark.addEventListener) { if (this.preferDark.addEventListener) {
this.preferDark.removeEventListener('change', this.onChange); this.preferDark.removeEventListener('change', this.onChange);
this.preferLight.removeEventListener('change', this.onChange); this.preferLight.removeEventListener('change', this.onChange);
this.preferHighContrast.removeEventListener('change', this.onChange);
} }
SettingsStore.unwatchSetting(this.systemThemeWatchRef); SettingsStore.unwatchSetting(this.systemThemeWatchRef);
SettingsStore.unwatchSetting(this.themeWatchRef); SettingsStore.unwatchSetting(this.themeWatchRef);
@ -108,8 +112,10 @@ export default class ThemeWatcher {
SettingLevel.DEVICE, "use_system_theme", null, false, true); SettingLevel.DEVICE, "use_system_theme", null, false, true);
if (systemThemeExplicit) { if (systemThemeExplicit) {
logger.log("returning explicit system theme"); logger.log("returning explicit system theme");
if (this.preferDark.matches) return 'dark'; const theme = this.themeBasedOnSystem();
if (this.preferLight.matches) return 'light'; if (theme) {
return theme;
}
} }
// If the user has specifically enabled the theme (without the system matching option being // If the user has specifically enabled the theme (without the system matching option being
@ -125,13 +131,31 @@ export default class ThemeWatcher {
// If the user hasn't really made a preference in either direction, assume the defaults of the // If the user hasn't really made a preference in either direction, assume the defaults of the
// settings and use those. // settings and use those.
if (SettingsStore.getValue('use_system_theme')) { if (SettingsStore.getValue('use_system_theme')) {
if (this.preferDark.matches) return 'dark'; const theme = this.themeBasedOnSystem();
if (this.preferLight.matches) return 'light'; if (theme) {
return theme;
}
} }
logger.log("returning theme value"); logger.log("returning theme value");
return SettingsStore.getValue('theme'); return SettingsStore.getValue('theme');
} }
private themeBasedOnSystem() {
let newTheme: string;
if (this.preferDark.matches) {
newTheme = 'dark';
} else if (this.preferLight.matches) {
newTheme = 'light';
}
if (this.preferHighContrast.matches) {
const hcTheme = findHighContrastTheme(newTheme);
if (hcTheme) {
newTheme = hcTheme;
}
}
return newTheme;
}
public isSystemThemeSupported() { public isSystemThemeSupported() {
return this.preferDark.matches || this.preferLight.matches; return this.preferDark.matches || this.preferLight.matches;
} }