Skip to content

Commit 78e5230

Browse files
committed
Make themed widgets reflect the effective theme
So that widgets such as Element Call will show up in the right theme even if the app is set to match the system theme.
1 parent 980b922 commit 78e5230

File tree

4 files changed

+42
-11
lines changed

4 files changed

+42
-11
lines changed

src/components/structures/MatrixChat.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import ThemeController from "../../settings/controllers/ThemeController";
5050
import { startAnyRegistrationFlow } from "../../Registration";
5151
import ResizeNotifier from "../../utils/ResizeNotifier";
5252
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
53-
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
53+
import ThemeWatcher, { ThemeWatcherEvent } from "../../settings/watchers/ThemeWatcher";
5454
import { FontWatcher } from "../../settings/watchers/FontWatcher";
5555
import { storeRoomAliasInCache } from "../../RoomAliasCache";
5656
import ToastStore from "../../stores/ToastStore";
@@ -133,6 +133,7 @@ import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"
133133
import { LoginSplashView } from "./auth/LoginSplashView";
134134
import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
135135
import { InitialCryptoSetupStore } from "../../stores/InitialCryptoSetupStore";
136+
import { setTheme } from "../../theme";
136137

137138
// legacy export
138139
export { default as Views } from "../../Views";
@@ -467,6 +468,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
467468
this.themeWatcher = new ThemeWatcher();
468469
this.fontWatcher = new FontWatcher();
469470
this.themeWatcher.start();
471+
this.themeWatcher.on(ThemeWatcherEvent.Change, setTheme);
470472
this.fontWatcher.start();
471473

472474
initSentry(SdkConfig.get("sentry"));
@@ -499,6 +501,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
499501
public componentWillUnmount(): void {
500502
Lifecycle.stopMatrixClient();
501503
dis.unregister(this.dispatcherRef);
504+
this.themeWatcher?.off(ThemeWatcherEvent.Change, setTheme);
502505
this.themeWatcher?.stop();
503506
this.fontWatcher?.stop();
504507
UIStore.destroy();

src/components/views/dialogs/ModalWidgetDialog.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { OwnProfileStore } from "../../../stores/OwnProfileStore";
3333
import { arrayFastClone } from "../../../utils/arrays";
3434
import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
3535
import { ELEMENT_CLIENT_ID } from "../../../identifiers";
36-
import SettingsStore from "../../../settings/SettingsStore";
36+
import ThemeWatcher, { ThemeWatcherEvent } from "../../../settings/watchers/ThemeWatcher";
3737

3838
interface IProps {
3939
widgetDefinition: IModalWidgetOpenRequestData;
@@ -54,6 +54,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
5454
private readonly widget: Widget;
5555
private readonly possibleButtons: ModalButtonID[];
5656
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
57+
private readonly themeWatcher = new ThemeWatcher();
5758

5859
public state: IState = {
5960
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter((b) => b.disabled).map((b) => b.id),
@@ -77,13 +78,19 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
7778
}
7879

7980
public componentWillUnmount(): void {
81+
this.themeWatcher.off(ThemeWatcherEvent.Change, this.onThemeChange);
82+
this.themeWatcher.stop();
8083
if (!this.state.messaging) return;
8184
this.state.messaging.off("ready", this.onReady);
8285
this.state.messaging.off(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
8386
this.state.messaging.stop();
8487
}
8588

8689
private onReady = (): void => {
90+
this.themeWatcher.start();
91+
this.themeWatcher.on(ThemeWatcherEvent.Change, this.onThemeChange);
92+
// Theme may have changed while messaging was starting
93+
this.onThemeChange(this.themeWatcher.getEffectiveTheme());
8794
this.state.messaging?.sendWidgetConfig(this.props.widgetDefinition);
8895
};
8996

@@ -94,6 +101,10 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
94101
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.SetModalButtonEnabled}`, this.onButtonEnableToggle);
95102
};
96103

104+
private onThemeChange = (theme: string): void => {
105+
this.state.messaging?.updateTheme({ name: theme });
106+
};
107+
97108
private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>): void => {
98109
this.props.onFinished(true, ev.detail.data);
99110
};
@@ -127,7 +138,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
127138
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
128139
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
129140
clientId: ELEMENT_CLIENT_ID,
130-
clientTheme: SettingsStore.getValue("theme"),
141+
clientTheme: this.themeWatcher.getEffectiveTheme(),
131142
clientLanguage: getUserLanguage(),
132143
baseUrl: MatrixClientPeg.safeGet().baseUrl,
133144
});

src/settings/watchers/ThemeWatcher.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,25 @@ Please see LICENSE files in the repository root for full details.
88
*/
99

1010
import { logger } from "matrix-js-sdk/src/logger";
11+
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
1112

1213
import SettingsStore from "../SettingsStore";
1314
import dis from "../../dispatcher/dispatcher";
1415
import { Action } from "../../dispatcher/actions";
1516
import ThemeController from "../controllers/ThemeController";
16-
import { findHighContrastTheme, setTheme } from "../../theme";
17+
import { findHighContrastTheme } from "../../theme";
1718
import { ActionPayload } from "../../dispatcher/payloads";
1819
import { SettingLevel } from "../SettingLevel";
1920

20-
export default class ThemeWatcher {
21+
export enum ThemeWatcherEvent {
22+
Change = "change",
23+
}
24+
25+
interface ThemeWatcherEventHandlerMap {
26+
[ThemeWatcherEvent.Change]: (theme: string) => void;
27+
}
28+
29+
export default class ThemeWatcher extends TypedEventEmitter<ThemeWatcherEvent, ThemeWatcherEventHandlerMap> {
2130
private themeWatchRef?: string;
2231
private systemThemeWatchRef?: string;
2332
private dispatcherRef?: string;
@@ -29,6 +38,7 @@ export default class ThemeWatcher {
2938
private currentTheme: string;
3039

3140
public constructor() {
41+
super();
3242
// we have both here as each may either match or not match, so by having both
3343
// we can get the tristate of dark/light/unsupported
3444
this.preferDark = (<any>global).matchMedia("(prefers-color-scheme: dark)");
@@ -72,9 +82,7 @@ export default class ThemeWatcher {
7282
public recheck(forceTheme?: string): void {
7383
const oldTheme = this.currentTheme;
7484
this.currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme;
75-
if (oldTheme !== this.currentTheme) {
76-
setTheme(this.currentTheme);
77-
}
85+
if (oldTheme !== this.currentTheme) this.emit(ThemeWatcherEvent.Change, this.currentTheme);
7886
}
7987

8088
public getEffectiveTheme(): string {

src/stores/widgets/StopGapWidget.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
3737
import { OwnProfileStore } from "../OwnProfileStore";
3838
import WidgetUtils from "../../utils/WidgetUtils";
3939
import { IntegrationManagers } from "../../integrations/IntegrationManagers";
40-
import SettingsStore from "../../settings/SettingsStore";
4140
import { WidgetType } from "../../widgets/WidgetType";
4241
import ActiveWidgetStore from "../ActiveWidgetStore";
4342
import { objectShallowClone } from "../../utils/objects";
@@ -46,7 +45,7 @@ import { Action } from "../../dispatcher/actions";
4645
import { ElementWidgetActions, IHangupCallApiRequest, IViewRoomApiRequest } from "./ElementWidgetActions";
4746
import { ModalWidgetStore } from "../ModalWidgetStore";
4847
import { IApp, isAppWidget } from "../WidgetStore";
49-
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
48+
import ThemeWatcher, { ThemeWatcherEvent } from "../../settings/watchers/ThemeWatcher";
5049
import { getCustomTheme } from "../../theme";
5150
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
5251
import { ELEMENT_CLIENT_ID } from "../../identifiers";
@@ -153,6 +152,7 @@ export class StopGapWidget extends EventEmitter {
153152
private roomId?: string;
154153
private kind: WidgetKind;
155154
private readonly virtual: boolean;
155+
private readonly themeWatcher = new ThemeWatcher();
156156
private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
157157
// This promise will be called and needs to resolve before the widget will actually become sticky.
158158
private stickyPromise?: () => Promise<void>;
@@ -214,7 +214,7 @@ export class StopGapWidget extends EventEmitter {
214214
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
215215
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
216216
clientId: ELEMENT_CLIENT_ID,
217-
clientTheme: SettingsStore.getValue("theme"),
217+
clientTheme: this.themeWatcher.getEffectiveTheme(),
218218
clientLanguage: getUserLanguage(),
219219
deviceId: this.client.getDeviceId() ?? undefined,
220220
baseUrl: this.client.baseUrl,
@@ -246,6 +246,10 @@ export class StopGapWidget extends EventEmitter {
246246
return !!this.messaging;
247247
}
248248

249+
private onThemeChange = (theme: string): void => {
250+
this.messaging?.updateTheme({ name: theme });
251+
};
252+
249253
private onOpenModal = async (ev: CustomEvent<IModalWidgetOpenRequest>): Promise<void> => {
250254
ev.preventDefault();
251255
if (ModalWidgetStore.instance.canOpenModalWidget()) {
@@ -281,6 +285,11 @@ export class StopGapWidget extends EventEmitter {
281285
this.messaging.on("ready", () => {
282286
WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.roomId, this.messaging!);
283287
this.emit("ready");
288+
289+
this.themeWatcher.start();
290+
this.themeWatcher.on(ThemeWatcherEvent.Change, this.onThemeChange);
291+
// Theme may have changed while messaging was starting
292+
this.onThemeChange(this.themeWatcher.getEffectiveTheme());
284293
});
285294
this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified"));
286295
this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal);

0 commit comments

Comments
 (0)