diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index 1182124e010..05bb21ba3d9 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -1,3 +1,4 @@
+import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
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";
@@ -45,6 +46,7 @@ import { CipherType } from "@bitwarden/common/enums/cipherType";
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
+import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AppIdService } from "@bitwarden/common/services/appId.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
@@ -168,6 +170,7 @@ export default class MainBackground {
policyApiService: PolicyApiServiceAbstraction;
userVerificationApiService: UserVerificationApiServiceAbstraction;
syncNotifierService: SyncNotifierServiceAbstraction;
+ avatarUpdateService: AvatarUpdateServiceAbstraction;
// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
backgroundWindow = window;
@@ -565,6 +568,8 @@ export default class MainBackground {
this.stateService,
this.apiService
);
+
+ this.avatarUpdateService = new AvatarUpdateService(this.apiService, this.stateService);
}
async bootstrap() {
diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts
index 309edee28ce..1e0dee9e789 100644
--- a/apps/browser/src/background/runtime.background.ts
+++ b/apps/browser/src/background/runtime.background.ts
@@ -97,6 +97,7 @@ export default class RuntimeBackground {
await this.main.refreshBadge();
await this.main.refreshMenu();
}, 2000);
+ this.main.avatarUpdateService.loadColorFromState();
}
break;
case "openPopup":
diff --git a/apps/desktop/src/app/layout/account-switcher.component.html b/apps/desktop/src/app/layout/account-switcher.component.html
index f361c09939b..991e0dfb716 100644
--- a/apps/desktop/src/app/layout/account-switcher.component.html
+++ b/apps/desktop/src/app/layout/account-switcher.component.html
@@ -12,6 +12,7 @@
diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts
index 495c461a420..09222cbced0 100644
--- a/apps/desktop/src/app/layout/account-switcher.component.ts
+++ b/apps/desktop/src/app/layout/account-switcher.component.ts
@@ -16,6 +16,7 @@ type ActiveAccount = {
id: string;
name: string;
email: string;
+ avatarColor: string;
};
export class SwitcherAccount extends Account {
@@ -27,6 +28,8 @@ export class SwitcherAccount extends Account {
);
}
+ avatarColor: string;
+
private removeWebProtocolFromString(urlString: string) {
const regex = /http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?/g;
return urlString.replace(regex, "");
@@ -112,6 +115,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
id: await this.tokenService.getUserId(),
name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()),
email: await this.tokenService.getEmail(),
+ avatarColor: await this.stateService.getAvatarColor(),
};
} catch {
this.activeAccount = undefined;
@@ -162,6 +166,9 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
userId: userId,
});
switcherAccounts[userId] = new SwitcherAccount(baseAccounts[userId]);
+ switcherAccounts[userId].avatarColor = await this.stateService.getAvatarColor({
+ userId: userId,
+ });
}
return switcherAccounts;
}
diff --git a/apps/web/src/app/components/dynamic-avatar.component.ts b/apps/web/src/app/components/dynamic-avatar.component.ts
new file mode 100644
index 00000000000..ccc1c57cf48
--- /dev/null
+++ b/apps/web/src/app/components/dynamic-avatar.component.ts
@@ -0,0 +1,41 @@
+import { Component, Input, OnDestroy } from "@angular/core";
+import { Observable, Subject } from "rxjs";
+
+import { AvatarUpdateService } from "@bitwarden/common/abstractions/account/avatar-update.service";
+type SizeTypes = "xlarge" | "large" | "default" | "small" | "xsmall";
+@Component({
+ selector: "dynamic-avatar",
+ template: `
+
+
+ `,
+})
+export class DynamicAvatarComponent implements OnDestroy {
+ @Input() border = false;
+ @Input() id: string;
+ @Input() text: string;
+ @Input() title: string;
+ @Input() size: SizeTypes = "default";
+ color$: Observable;
+ private destroy$ = new Subject();
+
+ constructor(private accountUpdateService: AvatarUpdateService) {
+ if (this.text) {
+ this.text = this.text.toUpperCase();
+ }
+ this.color$ = this.accountUpdateService.avatarUpdate$;
+ }
+
+ async ngOnDestroy() {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/apps/web/src/app/components/selectable-avatar.component.ts b/apps/web/src/app/components/selectable-avatar.component.ts
new file mode 100644
index 00000000000..deb573c5db0
--- /dev/null
+++ b/apps/web/src/app/components/selectable-avatar.component.ts
@@ -0,0 +1,54 @@
+import { Component, EventEmitter, Input, Output } from "@angular/core";
+
+@Component({
+ selector: "selectable-avatar",
+ template: `
+
+
+ `,
+})
+export class SelectableAvatarComponent {
+ @Input() id: string;
+ @Input() text: string;
+ @Input() title: string;
+ @Input() color: string;
+ @Input() border = false;
+ @Input() selected = false;
+ @Output() select = new EventEmitter();
+
+ onFire() {
+ this.select.emit(this.color);
+ }
+
+ get classList() {
+ return ["tw-rounded-full tw-inline-block"]
+ .concat(["tw-cursor-pointer", "tw-outline", "tw-outline-solid", "tw-outline-offset-1"])
+ .concat(
+ this.selected
+ ? ["tw-outline-[3px]", "tw-outline-primary-500"]
+ : [
+ "tw-outline-0",
+ "hover:tw-outline-1",
+ "hover:tw-outline-primary-300",
+ "focus:tw-outline-2",
+ "focus:tw-outline-primary-500",
+ ]
+ );
+ }
+}
diff --git a/apps/web/src/app/layouts/navbar.component.html b/apps/web/src/app/layouts/navbar.component.html
index 0146a83af32..e1513f050c6 100644
--- a/apps/web/src/app/layouts/navbar.component.html
+++ b/apps/web/src/app/layouts/navbar.component.html
@@ -45,7 +45,7 @@
[bitMenuTriggerFor]="accountMenu"
class="tw-border-0 tw-bg-transparent tw-text-alt2 tw-opacity-70 hover:tw-opacity-90"
>
-
+