diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f54f1de1dd..62d83b1900 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -610,8 +610,6 @@ export default class MainBackground { migrationRunner, ); - this.themeStateService = new DefaultThemeStateService(this.globalStateProvider); - this.masterPasswordService = new MasterPasswordService( this.stateProvider, this.stateService, @@ -785,6 +783,11 @@ export default class MainBackground { this.authService, ); + this.themeStateService = new DefaultThemeStateService( + this.globalStateProvider, + this.configService, + ); + this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService); this.cipherService = new CipherService( diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts index 7ca073d51b..12f5c54040 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -59,8 +59,6 @@ export class AppearanceV2Component implements OnInit { { name: i18nService.t("systemDefault"), value: ThemeType.System }, { name: i18nService.t("light"), value: ThemeType.Light }, { name: i18nService.t("dark"), value: ThemeType.Dark }, - { name: "Nord", value: ThemeType.Nord }, - { name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark }, ]; } diff --git a/apps/desktop/src/app/services/desktop-theme.service.ts b/apps/desktop/src/app/services/desktop-theme.service.ts new file mode 100644 index 0000000000..321aff677d --- /dev/null +++ b/apps/desktop/src/app/services/desktop-theme.service.ts @@ -0,0 +1,25 @@ +import { map } from "rxjs"; + +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { GlobalStateProvider } from "@bitwarden/common/platform/state"; +import { + THEME_SELECTION, + ThemeStateService, +} from "@bitwarden/common/platform/theming/theme-state.service"; + +export class DesktopThemeStateService implements ThemeStateService { + private readonly selectedThemeState = this.globalStateProvider.get(THEME_SELECTION); + + selectedTheme$ = this.selectedThemeState.state$.pipe(map((theme) => theme ?? this.defaultTheme)); + + constructor( + private globalStateProvider: GlobalStateProvider, + private defaultTheme: ThemeType = ThemeType.System, + ) {} + + async setSelectedTheme(theme: ThemeType): Promise { + await this.selectedThemeState.update(() => theme, { + shouldUpdate: (currentTheme) => currentTheme !== theme, + }); + } +} diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index d4b51ca1c7..d5672f54c0 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -66,6 +66,7 @@ import { SystemService } from "@bitwarden/common/platform/services/system.servic import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService } from "@bitwarden/components"; @@ -93,6 +94,7 @@ import { SearchBarService } from "../layout/search/search-bar.service"; import { DesktopFileDownloadService } from "./desktop-file-download.service"; import { DesktopSetPasswordJitService } from "./desktop-set-password-jit.service"; +import { DesktopThemeStateService } from "./desktop-theme.service"; import { InitService } from "./init.service"; import { NativeMessagingManifestService } from "./native-messaging-manifest.service"; import { RendererCryptoFunctionService } from "./renderer-crypto-function.service"; @@ -212,6 +214,11 @@ const safeProviders: SafeProvider[] = [ useFactory: () => fromIpcSystemTheme(), deps: [], }), + safeProvider({ + provide: ThemeStateService, + useClass: DesktopThemeStateService, + deps: [GlobalStateProvider], + }), safeProvider({ provide: EncryptedMessageHandlerService, deps: [ diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 887c8fb626..5bf9373b03 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -33,6 +33,7 @@ import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -174,10 +175,10 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: ThemeStateService, - useFactory: (globalStateProvider: GlobalStateProvider) => + useFactory: (globalStateProvider: GlobalStateProvider, configService: ConfigService) => // Web chooses to have Light as the default theme - new DefaultThemeStateService(globalStateProvider, ThemeType.Light), - deps: [GlobalStateProvider], + new DefaultThemeStateService(globalStateProvider, configService, ThemeType.Light), + deps: [GlobalStateProvider, ConfigService], }), safeProvider({ provide: CLIENT_TYPE, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index cdf6a27390..4cdf5be865 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -364,7 +364,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: ThemeStateService, useClass: DefaultThemeStateService, - deps: [GlobalStateProvider], + deps: [GlobalStateProvider, ConfigService], }), safeProvider({ provide: AbstractThemingService, diff --git a/libs/common/src/platform/theming/theme-state.service.ts b/libs/common/src/platform/theming/theme-state.service.ts index 9c31733416..bb146be492 100644 --- a/libs/common/src/platform/theming/theme-state.service.ts +++ b/libs/common/src/platform/theming/theme-state.service.ts @@ -1,5 +1,7 @@ -import { Observable, map } from "rxjs"; +import { Observable, combineLatest, map } from "rxjs"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; +import { ConfigService } from "../abstractions/config/config.service"; import { ThemeType } from "../enums"; import { GlobalStateProvider, KeyDefinition, THEMING_DISK } from "../state"; @@ -16,17 +18,32 @@ export abstract class ThemeStateService { abstract setSelectedTheme(theme: ThemeType): Promise; } -const THEME_SELECTION = new KeyDefinition(THEMING_DISK, "selection", { +export const THEME_SELECTION = new KeyDefinition(THEMING_DISK, "selection", { deserializer: (s) => s, }); export class DefaultThemeStateService implements ThemeStateService { private readonly selectedThemeState = this.globalStateProvider.get(THEME_SELECTION); - selectedTheme$ = this.selectedThemeState.state$.pipe(map((theme) => theme ?? this.defaultTheme)); + selectedTheme$ = combineLatest([ + this.selectedThemeState.state$, + this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), + ]).pipe( + map(([theme, isExtensionRefresh]) => { + // The extension refresh should not allow for Nord or SolarizedDark + // Default the user to their system theme + if (isExtensionRefresh && [ThemeType.Nord, ThemeType.SolarizedDark].includes(theme)) { + return ThemeType.System; + } + + return theme; + }), + map((theme) => theme ?? this.defaultTheme), + ); constructor( private globalStateProvider: GlobalStateProvider, + private configService: ConfigService, private defaultTheme: ThemeType = ThemeType.System, ) {}