mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[refactor] Introduce ThemingService (#2943)
* [refactor] Introduce ThemingService * [refactor] Implement ThemingService for web * [refactor] Implement ThemingService on browser * [refactor] Implement ThemingService for desktop * [refactor] Remove deprecated platformUtils.service theme methods * [fix] Move ThemingService from libs/common to libs/angular * [fix] Simplify ThemeBuilder's constructor * [fix] Dont notify subscribers of null values from theme$ * [fix] Always notify PaymentComponent of theme changes
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { InjectionToken, Injector, LOCALE_ID, NgModule } from "@angular/core";
|
||||
|
||||
import { ThemingService } from "@bitwarden/angular/services/theming/theming.service";
|
||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
@@ -423,6 +425,10 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
||||
useClass: TwoFactorService,
|
||||
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: AbstractThemingService,
|
||||
useClass: ThemingService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class JslibServicesModule {}
|
||||
|
||||
6
libs/angular/src/services/theming/theme.ts
Normal file
6
libs/angular/src/services/theming/theme.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ThemeType } from "@bitwarden/common/enums/themeType";
|
||||
|
||||
export interface Theme {
|
||||
configuredTheme: ThemeType;
|
||||
effectiveTheme: ThemeType;
|
||||
}
|
||||
19
libs/angular/src/services/theming/themeBuilder.ts
Normal file
19
libs/angular/src/services/theming/themeBuilder.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ThemeType } from "@bitwarden/common/enums/themeType";
|
||||
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export class ThemeBuilder implements Theme {
|
||||
get effectiveTheme(): ThemeType {
|
||||
return this.configuredTheme != ThemeType.System ? this.configuredTheme : this.systemTheme;
|
||||
}
|
||||
|
||||
constructor(readonly configuredTheme: ThemeType, readonly systemTheme: ThemeType) {}
|
||||
|
||||
updateSystemTheme(systemTheme: ThemeType): ThemeBuilder {
|
||||
return new ThemeBuilder(this.configuredTheme, systemTheme);
|
||||
}
|
||||
|
||||
updateConfiguredTheme(configuredTheme: ThemeType): ThemeBuilder {
|
||||
return new ThemeBuilder(configuredTheme, this.systemTheme);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { ThemeType } from "@bitwarden/common/enums/themeType";
|
||||
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export abstract class AbstractThemingService {
|
||||
theme$: Observable<Theme>;
|
||||
monitorThemeChanges: () => Promise<void>;
|
||||
updateSystemTheme: (systemTheme: ThemeType) => void;
|
||||
updateConfiguredTheme: (theme: ThemeType) => Promise<void>;
|
||||
}
|
||||
71
libs/angular/src/services/theming/theming.service.ts
Normal file
71
libs/angular/src/services/theming/theming.service.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { MediaMatcher } from "@angular/cdk/layout";
|
||||
import { DOCUMENT } from "@angular/common";
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { BehaviorSubject, filter, fromEvent, Observable } from "rxjs";
|
||||
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { ThemeType } from "@bitwarden/common/enums/themeType";
|
||||
|
||||
import { Theme } from "./theme";
|
||||
import { ThemeBuilder } from "./themeBuilder";
|
||||
import { AbstractThemingService } from "./theming.service.abstraction";
|
||||
|
||||
@Injectable()
|
||||
export class ThemingService implements AbstractThemingService {
|
||||
private _theme = new BehaviorSubject<ThemeBuilder | null>(null);
|
||||
theme$: Observable<Theme> = this._theme.pipe(filter((x) => x !== null));
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private mediaMatcher: MediaMatcher,
|
||||
@Inject(DOCUMENT) private document: Document
|
||||
) {
|
||||
this.monitorThemeChanges();
|
||||
}
|
||||
|
||||
async monitorThemeChanges(): Promise<void> {
|
||||
this._theme.next(
|
||||
new ThemeBuilder(await this.stateService.getTheme(), await this.getSystemTheme())
|
||||
);
|
||||
this.monitorConfiguredThemeChanges();
|
||||
this.monitorSystemThemeChanges();
|
||||
}
|
||||
|
||||
updateSystemTheme(systemTheme: ThemeType): void {
|
||||
this._theme.next(this._theme.getValue().updateSystemTheme(systemTheme));
|
||||
}
|
||||
|
||||
async updateConfiguredTheme(theme: ThemeType): Promise<void> {
|
||||
await this.stateService.setTheme(theme);
|
||||
this._theme.next(this._theme.getValue().updateConfiguredTheme(theme));
|
||||
}
|
||||
|
||||
protected monitorConfiguredThemeChanges(): void {
|
||||
this.theme$.subscribe((theme: Theme) => {
|
||||
this.document.documentElement.classList.remove(
|
||||
"theme_" + ThemeType.Light,
|
||||
"theme_" + ThemeType.Dark,
|
||||
"theme_" + ThemeType.Nord,
|
||||
"theme_" + ThemeType.SolarizedDark
|
||||
);
|
||||
this.document.documentElement.classList.add("theme_" + theme.effectiveTheme);
|
||||
});
|
||||
}
|
||||
|
||||
// We use a media match query for monitoring the system theme on web and browser, but this doesn't work for electron apps on Linux.
|
||||
// In desktop we override these methods to track systemTheme with the electron renderer instead, which works for all OSs.
|
||||
protected async getSystemTheme(): Promise<ThemeType> {
|
||||
return this.mediaMatcher.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? ThemeType.Dark
|
||||
: ThemeType.Light;
|
||||
}
|
||||
|
||||
protected monitorSystemThemeChanges(): void {
|
||||
fromEvent<MediaQueryListEvent>(
|
||||
this.mediaMatcher.matchMedia("(prefers-color-scheme: dark)"),
|
||||
"change"
|
||||
).subscribe((event) => {
|
||||
this.updateSystemTheme(event.matches ? ThemeType.Dark : ThemeType.Light);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user