From d212bb1fd08b24f345bbb45423260f902b8ad750 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:01:47 -0500 Subject: [PATCH] [PM-7713] Refresh Appearance Settings (#10458) * add v2 of appearance component * swap in new appearance component based on refresh flag * update default theme verbiage --- apps/browser/src/_locales/en/messages.json | 6 + apps/browser/src/popup/app-routing.module.ts | 6 +- .../settings/appearance-v2.component.html | 32 +++++ .../settings/appearance-v2.component.spec.ts | 110 ++++++++++++++++++ .../popup/settings/appearance-v2.component.ts | 110 ++++++++++++++++++ 5 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 apps/browser/src/vault/popup/settings/appearance-v2.component.html create mode 100644 apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts create mode 100644 apps/browser/src/vault/popup/settings/appearance-v2.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 6c41706ddd7..1af1cb66387 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4164,5 +4164,11 @@ }, "accountActions": { "message": "Account actions" + }, + "showNumberOfAutofillSuggestions": { + "message": "Show number of login autofill suggestions on extension icon" + }, + "systemDefault": { + "message": "System default" } } diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 16e12c3d75c..b7b4e41778b 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -79,6 +79,7 @@ import { AddEditV2Component } from "../vault/popup/components/vault-v2/add-edit/ import { AssignCollections } from "../vault/popup/components/vault-v2/assign-collections/assign-collections.component"; import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attachments/attachments-v2.component"; import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component"; +import { AppearanceV2Component } from "../vault/popup/settings/appearance-v2.component"; import { AppearanceComponent } from "../vault/popup/settings/appearance.component"; import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component"; @@ -339,12 +340,11 @@ const routes: Routes = [ canActivate: [authGuard], data: { state: "premium" }, }, - { + ...extensionRefreshSwap(AppearanceComponent, AppearanceV2Component, { path: "appearance", - component: AppearanceComponent, canActivate: [authGuard], data: { state: "appearance" }, - }, + }), ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { path: "clone-cipher", canActivate: [authGuard], diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.html b/apps/browser/src/vault/popup/settings/appearance-v2.component.html new file mode 100644 index 00000000000..565699a6f5b --- /dev/null +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.html @@ -0,0 +1,32 @@ + + + + + + + +
+ + + {{ "theme" | i18n }} + + + + + + + + {{ "showNumberOfAutofillSuggestions" | i18n }} + + + + + {{ "enableFavicon" | i18n }} + + +
+
diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts new file mode 100644 index 00000000000..69186359e2b --- /dev/null +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts @@ -0,0 +1,110 @@ +import { Component, Input } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; + +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +import { AppearanceV2Component } from "./appearance-v2.component"; + +@Component({ + standalone: true, + selector: "popup-header", + template: ``, +}) +class MockPopupHeaderComponent { + @Input() pageTitle: string; + @Input() backAction: () => void; +} + +@Component({ + standalone: true, + selector: "popup-page", + template: ``, +}) +class MockPopupPageComponent {} + +describe("AppearanceV2Component", () => { + let component: AppearanceV2Component; + let fixture: ComponentFixture; + + const showFavicons$ = new BehaviorSubject(true); + const enableBadgeCounter$ = new BehaviorSubject(true); + const selectedTheme$ = new BehaviorSubject(ThemeType.Nord); + const setSelectedTheme = jest.fn().mockResolvedValue(undefined); + const setShowFavicons = jest.fn().mockResolvedValue(undefined); + const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined); + + beforeEach(async () => { + setSelectedTheme.mockClear(); + setShowFavicons.mockClear(); + setEnableBadgeCounter.mockClear(); + + await TestBed.configureTestingModule({ + imports: [AppearanceV2Component], + providers: [ + { provide: ConfigService, useValue: mock() }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: MessagingService, useValue: mock() }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: DomainSettingsService, useValue: { showFavicons$, setShowFavicons } }, + { + provide: BadgeSettingsServiceAbstraction, + useValue: { enableBadgeCounter$, setEnableBadgeCounter }, + }, + { provide: ThemeStateService, useValue: { selectedTheme$, setSelectedTheme } }, + ], + }) + .overrideComponent(AppearanceV2Component, { + remove: { + imports: [PopupHeaderComponent, PopupPageComponent], + }, + add: { + imports: [MockPopupHeaderComponent, MockPopupPageComponent], + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(AppearanceV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("populates the form with the user's current settings", () => { + expect(component.appearanceForm.value).toEqual({ + enableFavicon: true, + enableBadgeCounter: true, + theme: ThemeType.Nord, + }); + }); + + describe("form changes", () => { + it("updates the users theme", () => { + component.appearanceForm.controls.theme.setValue(ThemeType.Light); + + expect(setSelectedTheme).toHaveBeenCalledWith(ThemeType.Light); + }); + + it("updates the users favicon setting", () => { + component.appearanceForm.controls.enableFavicon.setValue(false); + + expect(setShowFavicons).toHaveBeenCalledWith(false); + }); + + it("updates the users badge counter setting", () => { + component.appearanceForm.controls.enableBadgeCounter.setValue(false); + + expect(setEnableBadgeCounter).toHaveBeenCalledWith(false); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts new file mode 100644 index 00000000000..7ca073d51b0 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -0,0 +1,110 @@ +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { CheckboxModule } from "@bitwarden/components"; + +import { CardComponent } from "../../../../../../libs/components/src/card/card.component"; +import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module"; +import { SelectModule } from "../../../../../../libs/components/src/select/select.module"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +@Component({ + standalone: true, + templateUrl: "./appearance-v2.component.html", + imports: [ + CommonModule, + JslibModule, + PopupPageComponent, + PopupHeaderComponent, + PopOutComponent, + CardComponent, + FormFieldModule, + SelectModule, + ReactiveFormsModule, + CheckboxModule, + ], +}) +export class AppearanceV2Component implements OnInit { + appearanceForm = this.formBuilder.group({ + enableFavicon: false, + enableBadgeCounter: true, + theme: ThemeType.System, + }); + + /** Available theme options */ + themeOptions: { name: string; value: ThemeType }[]; + + constructor( + private messagingService: MessagingService, + private domainSettingsService: DomainSettingsService, + private badgeSettingsService: BadgeSettingsServiceAbstraction, + private themeStateService: ThemeStateService, + private formBuilder: FormBuilder, + private destroyRef: DestroyRef, + i18nService: I18nService, + ) { + this.themeOptions = [ + { 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 }, + ]; + } + + async ngOnInit() { + const enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$); + const enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$); + const theme = await firstValueFrom(this.themeStateService.selectedTheme$); + + // Set initial values for the form + this.appearanceForm.setValue({ + enableFavicon, + enableBadgeCounter, + theme, + }); + + this.appearanceForm.controls.theme.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((newTheme) => { + void this.saveTheme(newTheme); + }); + + this.appearanceForm.controls.enableFavicon.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enableFavicon) => { + void this.updateFavicon(enableFavicon); + }); + + this.appearanceForm.controls.enableBadgeCounter.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enableBadgeCounter) => { + void this.updateBadgeCounter(enableBadgeCounter); + }); + } + + async updateFavicon(enableFavicon: boolean) { + await this.domainSettingsService.setShowFavicons(enableFavicon); + } + + async updateBadgeCounter(enableBadgeCounter: boolean) { + await this.badgeSettingsService.setEnableBadgeCounter(enableBadgeCounter); + this.messagingService.send("bgUpdateContextMenu"); + } + + async saveTheme(newTheme: ThemeType) { + await this.themeStateService.setSelectedTheme(newTheme); + } +}