diff --git a/res/themes/light-high-contrast/css/_light-high-contrast.scss b/res/themes/light-high-contrast/css/_light-high-contrast.scss index f1f4387a06..ef23fdfef1 100644 --- a/res/themes/light-high-contrast/css/_light-high-contrast.scss +++ b/res/themes/light-high-contrast/css/_light-high-contrast.scss @@ -2,6 +2,7 @@ $accent: #268075; $alert: #D62C25; $links: #0A6ECA; +$primary-content: #17191C; $secondary-content: #5E6266; $tertiary-content: $secondary-content; $quaternary-content: $secondary-content; @@ -106,3 +107,11 @@ $roomtopic-color: $secondary-content; .mx_FontScalingPanel_fontSlider { 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; +} diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 646fc32b59..4a533f1f8e 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -32,7 +32,7 @@ import FeedbackDialog from "../views/dialogs/FeedbackDialog"; import Modal from "../../Modal"; import LogoutDialog from "../views/dialogs/LogoutDialog"; import SettingsStore from "../../settings/SettingsStore"; -import { getCustomTheme } from "../../theme"; +import { findHighContrastTheme, getCustomTheme, isHighContrastTheme } from "../../theme"; import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; import SdkConfig from "../../SdkConfig"; import { getHomePageUrl } from "../../utils/pages"; @@ -69,6 +69,7 @@ type PartialDOMRect = Pick; interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; + isHighContrast: boolean; selectedSpace?: Room; pendingRoomJoin: Set; } @@ -87,6 +88,7 @@ export default class UserMenu extends React.Component { this.state = { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), + isHighContrast: this.isUserOnHighContrastTheme(), pendingRoomJoin: new Set(), }; @@ -142,6 +144,18 @@ export default class UserMenu extends React.Component { } } + 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 () => { // the store triggered an update, so force a layout update. We don't // have any state to store here for that to magically happen. @@ -153,7 +167,11 @@ export default class UserMenu extends React.Component { }; private onThemeChanged = () => { - this.setState({ isDarkTheme: this.isUserOnDarkTheme() }); + this.setState( + { + isDarkTheme: this.isUserOnDarkTheme(), + isHighContrast: this.isUserOnHighContrastTheme(), + }); }; private onAction = (ev: ActionPayload) => { @@ -221,7 +239,13 @@ export default class UserMenu extends React.Component { // Disable system theme matching if the user hits this button 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 }; diff --git a/src/settings/watchers/ThemeWatcher.ts b/src/settings/watchers/ThemeWatcher.ts index e555267706..d8f25b5075 100644 --- a/src/settings/watchers/ThemeWatcher.ts +++ b/src/settings/watchers/ThemeWatcher.ts @@ -19,7 +19,7 @@ import SettingsStore from '../SettingsStore'; import dis from '../../dispatcher/dispatcher'; import { Action } from '../../dispatcher/actions'; import ThemeController from "../controllers/ThemeController"; -import { setTheme } from "../../theme"; +import { findHighContrastTheme, setTheme } from "../../theme"; import { ActionPayload } from '../../dispatcher/payloads'; import { SettingLevel } from "../SettingLevel"; @@ -32,6 +32,7 @@ export default class ThemeWatcher { private preferDark: MediaQueryList; private preferLight: MediaQueryList; + private preferHighContrast: MediaQueryList; private currentTheme: string; @@ -44,6 +45,7 @@ export default class ThemeWatcher { // 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.preferHighContrast = (global).matchMedia("(prefers-contrast: more)"); this.currentTheme = this.getEffectiveTheme(); } @@ -54,6 +56,7 @@ export default class ThemeWatcher { if (this.preferDark.addEventListener) { this.preferDark.addEventListener('change', this.onChange); this.preferLight.addEventListener('change', this.onChange); + this.preferHighContrast.addEventListener('change', this.onChange); } this.dispatcherRef = dis.register(this.onAction); } @@ -62,6 +65,7 @@ export default class ThemeWatcher { if (this.preferDark.addEventListener) { this.preferDark.removeEventListener('change', this.onChange); this.preferLight.removeEventListener('change', this.onChange); + this.preferHighContrast.removeEventListener('change', this.onChange); } SettingsStore.unwatchSetting(this.systemThemeWatchRef); SettingsStore.unwatchSetting(this.themeWatchRef); @@ -108,8 +112,10 @@ export default class ThemeWatcher { SettingLevel.DEVICE, "use_system_theme", null, false, true); if (systemThemeExplicit) { logger.log("returning explicit system theme"); - if (this.preferDark.matches) return 'dark'; - if (this.preferLight.matches) return 'light'; + const theme = this.themeBasedOnSystem(); + if (theme) { + return theme; + } } // 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 // settings and use those. if (SettingsStore.getValue('use_system_theme')) { - if (this.preferDark.matches) return 'dark'; - if (this.preferLight.matches) return 'light'; + const theme = this.themeBasedOnSystem(); + if (theme) { + return theme; + } } logger.log("returning theme value"); 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() { return this.preferDark.matches || this.preferLight.matches; }