1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +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:
Addison Beck
2022-06-23 04:36:05 -07:00
committed by GitHub
parent fd69e163ff
commit 57b8144013
21 changed files with 188 additions and 159 deletions

View File

@@ -1,4 +1,5 @@
import { DragDropModule } from "@angular/cdk/drag-drop";
import { LayoutModule } from "@angular/cdk/layout";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
@@ -18,6 +19,7 @@ import { WildcardRoutingModule } from "./wildcard-routing.module";
ServicesModule,
InfiniteScrollModule,
DragDropModule,
LayoutModule,
OssRoutingModule,
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
],

View File

@@ -1,6 +1,7 @@
import { Inject, Injectable } from "@angular/core";
import { WINDOW } from "@bitwarden/angular/services/jslib-services.module";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
import {
EnvironmentService as EnvironmentServiceAbstraction,
@@ -9,11 +10,9 @@ import {
import { EventService as EventLoggingServiceAbstraction } from "@bitwarden/common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/abstractions/twoFactor.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout.service";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { EventService as EventLoggingService } from "@bitwarden/common/services/event.service";
import { VaultTimeoutService as VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout.service";
@@ -31,8 +30,8 @@ export class InitService {
private eventLoggingService: EventLoggingServiceAbstraction,
private twoFactorService: TwoFactorServiceAbstraction,
private stateService: StateServiceAbstraction,
private platformUtilsService: PlatformUtilsServiceAbstraction,
private cryptoService: CryptoServiceAbstraction
private cryptoService: CryptoServiceAbstraction,
private themingService: AbstractThemingService
) {}
init() {
@@ -44,7 +43,6 @@ export class InitService {
this.environmentService.setUrls(urls);
setTimeout(() => this.notificationsService.init(), 3000);
(this.vaultTimeoutService as VaultTimeoutService).init(true);
const locale = await this.stateService.getLocale();
await (this.i18nService as I18nService).init(locale);
@@ -52,16 +50,7 @@ export class InitService {
this.twoFactorService.init();
const htmlEl = this.win.document.documentElement;
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
// Initial theme is set in index.html which must be updated if there are any changes to theming logic
this.platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
const bwTheme = await this.stateService.getTheme();
if (bwTheme === ThemeType.System) {
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add("theme_" + sysTheme);
}
});
await this.themingService.monitorThemeChanges();
const containerService = new ContainerService(this.cryptoService);
containerService.attachToWindow(this.win);
};

View File

@@ -1,8 +1,9 @@
import { Component, Input, OnInit } from "@angular/core";
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PaymentMethodType } from "@bitwarden/common/enums/paymentMethodType";
import { ThemeType } from "@bitwarden/common/enums/themeType";
@@ -17,7 +18,7 @@ const darkInputPlaceholderColor = ThemeVariables.darkInputPlaceholderColor;
selector: "app-payment",
templateUrl: "payment.component.html",
})
export class PaymentComponent implements OnInit {
export class PaymentComponent implements OnInit, OnDestroy {
@Input() showMethods = true;
@Input() showOptions = true;
@Input() method = PaymentMethodType.Card;
@@ -25,6 +26,8 @@ export class PaymentComponent implements OnInit {
@Input() hidePaypal = false;
@Input() hideCredit = false;
private destroy$: Subject<void> = new Subject<void>();
bank: any = {
routing_number: null,
account_number: null,
@@ -48,9 +51,9 @@ export class PaymentComponent implements OnInit {
private StripeElementClasses: any;
constructor(
private platformUtilsService: PlatformUtilsService,
private apiService: ApiService,
private logService: LogService
private logService: LogService,
private themingService: AbstractThemingService
) {
this.stripeScript = window.document.createElement("script");
this.stripeScript.src = "https://js.stripe.com/v3/";
@@ -100,6 +103,8 @@ export class PaymentComponent implements OnInit {
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
window.document.head.removeChild(this.stripeScript);
window.setTimeout(() => {
Array.from(window.document.querySelectorAll("iframe")).forEach((el) => {
@@ -275,15 +280,16 @@ export class PaymentComponent implements OnInit {
}
private async setTheme() {
const theme = await this.platformUtilsService.getEffectiveTheme();
if (theme === ThemeType.Dark) {
this.StripeElementStyle.base.color = darkInputColor;
this.StripeElementStyle.base["::placeholder"].color = darkInputPlaceholderColor;
this.StripeElementStyle.invalid.color = darkInputColor;
} else {
this.StripeElementStyle.base.color = lightInputColor;
this.StripeElementStyle.base["::placeholder"].color = lightInputPlaceholderColor;
this.StripeElementStyle.invalid.color = lightInputColor;
}
this.themingService.theme$.pipe(takeUntil(this.destroy$)).subscribe((theme) => {
if (theme.effectiveTheme === ThemeType.Dark) {
this.StripeElementStyle.base.color = darkInputColor;
this.StripeElementStyle.base["::placeholder"].color = darkInputPlaceholderColor;
this.StripeElementStyle.invalid.color = darkInputColor;
} else {
this.StripeElementStyle.base.color = lightInputColor;
this.StripeElementStyle.base["::placeholder"].color = lightInputPlaceholderColor;
this.StripeElementStyle.invalid.color = lightInputColor;
}
});
}
}

View File

@@ -1,6 +1,7 @@
import { Component, OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -34,7 +35,8 @@ export class PreferencesComponent implements OnInit {
private i18nService: I18nService,
private vaultTimeoutService: VaultTimeoutService,
private platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService
private messagingService: MessagingService,
private themingService: AbstractThemingService
) {
this.vaultTimeouts = [
{ name: i18nService.t("oneMinute"), value: 1 },
@@ -100,12 +102,8 @@ export class PreferencesComponent implements OnInit {
await this.stateService.setEnableFullWidth(this.enableFullWidth);
this.messagingService.send("setFullWidth");
if (this.theme !== this.startingTheme) {
await this.stateService.setTheme(this.theme);
await this.themingService.updateConfiguredTheme(this.theme);
this.startingTheme = this.theme;
const effectiveTheme = await this.platformUtilsService.getEffectiveTheme();
const htmlEl = window.document.documentElement;
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add("theme_" + effectiveTheme);
}
await this.stateService.setLocale(this.locale);
if (this.locale !== this.startingLocale) {

View File

@@ -5,21 +5,17 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { ClientType } from "@bitwarden/common/enums/clientType";
import { DeviceType } from "@bitwarden/common/enums/deviceType";
import { ThemeType } from "@bitwarden/common/enums/themeType";
@Injectable()
export class WebPlatformUtilsService implements PlatformUtilsService {
private browserCache: DeviceType = null;
private prefersColorSchemeDark = window.matchMedia("(prefers-color-scheme: dark)");
constructor(
private i18nService: I18nService,
private messagingService: MessagingService,
private logService: LogService,
private stateService: StateService
private logService: LogService
) {}
getDevice(): DeviceType {
@@ -303,32 +299,4 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
supportsSecureStorage() {
return false;
}
getDefaultSystemTheme(): Promise<ThemeType.Light | ThemeType.Dark> {
return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light);
}
async getEffectiveTheme(): Promise<ThemeType.Light | ThemeType.Dark> {
const theme = await this.stateService.getTheme();
if (theme === ThemeType.Dark) {
return ThemeType.Dark;
} else if (theme === ThemeType.System) {
return this.getDefaultSystemTheme();
} else {
return ThemeType.Light;
}
}
onDefaultSystemThemeChange(callback: (theme: ThemeType.Light | ThemeType.Dark) => unknown) {
try {
this.prefersColorSchemeDark.addEventListener("change", ({ matches }) => {
callback(matches ? ThemeType.Dark : ThemeType.Light);
});
} catch (e) {
// Safari older than v14
this.prefersColorSchemeDark.addListener((ev) => {
callback(ev.matches ? ThemeType.Dark : ThemeType.Light);
});
}
}
}