From eba5f6f1cee938dbbc67527548cf7f2ffa73296b Mon Sep 17 00:00:00 2001 From: jaasen-livefront Date: Tue, 20 Jan 2026 18:11:27 -0800 Subject: [PATCH] autofill info dialog --- apps/browser/src/_locales/en/messages.json | 6 ++ ...ill-suggestions-info-dialog.component.html | 13 +++ ...ofill-suggestions-info-dialog.component.ts | 18 ++++ .../autofill-vault-list-items.component.html | 4 + .../autofill-vault-list-items.component.ts | 98 +++++++++++++++-- .../vault-list-items-container.component.html | 49 ++++++++- .../vault-list-items-container.component.ts | 100 ++++++++---------- ...popup-autofill-suggestions-info.service.ts | 57 ++++++++++ 8 files changed, 275 insertions(+), 70 deletions(-) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.html create mode 100644 apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.ts create mode 100644 apps/browser/src/vault/popup/services/vault-popup-autofill-suggestions-info.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 90cc4a5c338..58f758d0d97 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3765,6 +3765,12 @@ "autofillSelectInfoWithoutCommand": { "message": "Select an item from this screen, or explore other options in settings." }, + "simplifiedAutofill": { + "message": "Simplified autofill" + }, + "simplifiedAutofillDesc": { + "message": "Now, when you click a suggested autofill item, it fills rather than taking you to details. You can still view these items from the More menu." + }, "gotIt": { "message": "Got it" }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.html new file mode 100644 index 00000000000..c5f20ec520b --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.html @@ -0,0 +1,13 @@ + +
+

+ {{ "autofillSelectInfoWithoutCommand" | i18n }} +

+
+ + + + +
diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.ts new file mode 100644 index 00000000000..73f50048f09 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-suggestions-info-dialog/autofill-suggestions-info-dialog.component.ts @@ -0,0 +1,18 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ButtonModule, DialogModule, DialogRef, TypographyModule } from "@bitwarden/components"; + +@Component({ + templateUrl: "./autofill-suggestions-info-dialog.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, DialogModule, TypographyModule, ButtonModule, JslibModule], +}) +export class AutofillSuggestionsInfoDialogComponent { + private dialogRef = inject(DialogRef); + + close() { + this.dialogRef.close(); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html index 47ef0284d6a..282bbf75185 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html @@ -5,6 +5,10 @@ [showRefresh]="showRefresh" (onRefresh)="refreshCurrentTab()" [description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : undefined" + [showInfoIcon]="showInfoIcon$ | async" + [pingInfoIcon]="pingInfoIcon()" + (onInfoIconClick)="openAutofillSuggestionsInfo()" + (onInfoIconDismissed)="dismissAutofillSuggestionsInfo()" showAutofillButton [disableDescriptionMargin]="showEmptyAutofillTip$ | async" [primaryActionAutofill]="clickItemsToAutofillVaultView$ | async" diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index 64f662ab840..4a2355cdf56 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -1,15 +1,18 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { toSignal } from "@angular/core/rxjs-interop"; -import { combineLatest, map, Observable, startWith } from "rxjs"; +import { Component, DestroyRef, OnInit, inject, signal } from "@angular/core"; +import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; +import { combineLatest, filter, from, map, Observable, startWith, switchMap, take } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { IconButtonModule, TypographyModule } from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../platform/browser/browser-popup-utils"; +import { VaultPopupAutofillSuggestionsInfoService } from "../../../services/vault-popup-autofill-suggestions-info.service"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; import { PopupCipherViewLike } from "../../../views/popup-cipher.view"; @@ -28,7 +31,10 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/ selector: "app-autofill-vault-list-items", templateUrl: "autofill-vault-list-items.component.html", }) -export class AutofillVaultListItemsComponent { +export class AutofillVaultListItemsComponent implements OnInit { + private readonly destroyRef = inject(DestroyRef); + private readonly activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); + /** * The list of ciphers that can be used to autofill the current page. * @protected @@ -48,6 +54,35 @@ export class AutofillVaultListItemsComponent { startWith(true), // Start with true to avoid flashing the fill button on first load ); + /** When true, the info icon should ping (4 iterations). */ + protected readonly pingInfoIcon = signal(false); + + /** Flag indicating that the current tab location is blocked */ + protected readonly currentURIIsBlocked$: Observable = + this.vaultPopupAutofillService.currentTabIsOnBlocklist$; + + /** Computed state for the per-user info icon (dismissed/ping completed). */ + private readonly infoState$ = this.activeUserId$.pipe( + switchMap((userId) => this.autofillSuggestionsInfoService.state$(userId)), + ); + + /** + * Show the info icon only when: + * - the current page is not blocked + * - the autofill suggestions list is populated + * - the user hasn't dismissed the info dialog + */ + protected readonly showInfoIcon$ = combineLatest([ + this.autofillCiphers$, + this.currentURIIsBlocked$, + this.infoState$, + ]).pipe( + map(([ciphers, isBlocked, infoState]) => { + const hasItems = (ciphers ?? []).length > 0; + return !isBlocked && hasItems && !(infoState.dismissed ?? false); + }), + ); + protected readonly groupByType = toSignal( this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)), ); @@ -71,18 +106,61 @@ export class AutofillVaultListItemsComponent { ), ); - /** - * Flag indicating that the current tab location is blocked - */ - currentURIIsBlocked$: Observable = - this.vaultPopupAutofillService.currentTabIsOnBlocklist$; - constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupAutofillService: VaultPopupAutofillService, private vaultSettingsService: VaultSettingsService, + private accountService: AccountService, + private autofillSuggestionsInfoService: VaultPopupAutofillSuggestionsInfoService, ) {} + ngOnInit() { + // Start the ping animation once (4 iterations) the first time suggestions become populated. + combineLatest([ + this.activeUserId$, + this.autofillCiphers$, + this.currentURIIsBlocked$, + this.infoState$, + ]) + .pipe( + takeUntilDestroyed(this.destroyRef), + filter(([_userId, ciphers, isBlocked, infoState]) => { + const hasItems = (ciphers ?? []).length > 0; + return ( + !isBlocked && + hasItems && + !(infoState.dismissed ?? false) && + !(infoState.pingCompleted ?? false) + ); + }), + take(1), + ) + .subscribe(([userId]) => { + // Mark completed immediately so it never replays across vault opens. + void this.autofillSuggestionsInfoService.markPingCompleted(userId); + + this.pingInfoIcon.set(true); + window.setTimeout(() => this.pingInfoIcon.set(false), 4000); + }); + } + + protected openAutofillSuggestionsInfo(): void { + // Keep the icon visible until the user dismisses the popover, but stop any ping immediately. + this.pingInfoIcon.set(false); + } + + protected dismissAutofillSuggestionsInfo(): void { + this.pingInfoIcon.set(false); + + this.activeUserId$ + .pipe( + take(1), + takeUntilDestroyed(this.destroyRef), + switchMap((userId) => from(this.autofillSuggestionsInfoService.markDismissed(userId))), + ) + .subscribe(); + } + /** * Refreshes the current tab to re-populate the autofill ciphers. * @protected diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index 3dac158b8e1..90096b4d380 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -33,9 +33,52 @@ -

- {{ title() }} -

+
+

+ {{ title() }} +

+ + @if (showInfoIcon()) { + + + + +

{{ "simplifiedAutofillDesc" | i18n }}

+
+ +
+
+
+ } +