Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
$accent: #268075;
$alert: #D62C25;
$links: #0A6ECA;
$primary-content: #17191C;
$secondary-content: #5E6266;
$tertiary-content: $secondary-content;
$quaternary-content: $secondary-content;
Expand Down Expand Up @@ -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;
}
30 changes: 27 additions & 3 deletions src/components/structures/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -69,6 +69,7 @@ type PartialDOMRect = Pick<DOMRect, "width" | "left" | "top" | "height">;
interface IState {
contextMenuPosition: PartialDOMRect;
isDarkTheme: boolean;
isHighContrast: boolean;
selectedSpace?: Room;
pendingRoomJoin: Set<string>;
}
Expand All @@ -87,6 +88,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
this.state = {
contextMenuPosition: null,
isDarkTheme: this.isUserOnDarkTheme(),
isHighContrast: this.isUserOnHighContrastTheme(),
pendingRoomJoin: new Set<string>(),
};

Expand Down Expand Up @@ -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 () => {
// the store triggered an update, so force a layout update. We don't
// have any state to store here for that to magically happen.
Expand All @@ -153,7 +167,11 @@ export default class UserMenu extends React.Component<IProps, IState> {
};

private onThemeChanged = () => {
this.setState({ isDarkTheme: this.isUserOnDarkTheme() });
this.setState(
{
isDarkTheme: this.isUserOnDarkTheme(),
isHighContrast: this.isUserOnHighContrastTheme(),
});
};

private onAction = (ev: ActionPayload) => {
Expand Down Expand Up @@ -221,7 +239,13 @@ export default class UserMenu extends React.Component<IProps, IState> {
// 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
};

Expand Down
34 changes: 29 additions & 5 deletions src/settings/watchers/ThemeWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -32,6 +32,7 @@ export default class ThemeWatcher {

private preferDark: MediaQueryList;
private preferLight: MediaQueryList;
private preferHighContrast: MediaQueryList;

private currentTheme: string;

Expand All @@ -44,6 +45,7 @@ export default class ThemeWatcher {
// we can get the tristate of dark/light/unsupported
this.preferDark = (<any>global).matchMedia("(prefers-color-scheme: dark)");
this.preferLight = (<any>global).matchMedia("(prefers-color-scheme: light)");
this.preferHighContrast = (<any>global).matchMedia("(prefers-contrast: more)");

this.currentTheme = this.getEffectiveTheme();
}
Expand All @@ -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);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand Down