chromium/chrome/browser/resources/ash/settings/os_apps_page/app_notifications_page/app_notifications_manager_subpage.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * 'app-notifications-manager-subpage' is responsible for containing controls
 * for sending notifications for the apps.
 */

import 'chrome://resources/ash/common/cr_elements/cr_shared_style.css.js';
import './app_notification_row.js';

import {assert} from 'chrome://resources/js/assert.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {isRevampWayfindingEnabled} from '../../common/load_time_booleans.js';
import {App, AppNotificationsHandlerInterface, AppNotificationsObserverReceiver} from '../../mojom-webui/app_notification_handler.mojom-webui.js';
import {isAppInstalled} from '../os_apps_page.js';

import {getTemplate} from './app_notifications_manager_subpage.html.js';
import {getAppNotificationProvider} from './mojo_interface_provider.js';

export class SettingsAppNotificationsManagerSubpage extends PolymerElement {
  static get is() {
    return 'settings-app-notifications-manager-subpage' as const;
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      searchTerm: {
        type: String,
      },

      appList_: {
        type: Array,
        value: [],
      },

      /**
       * List of apps filtered through a search term.
       */
      filteredAppList_: {
        type: Array,
        value: () => [],
        computed: 'computeFilteredAppList_(appList_.*, searchTerm)',
      },
    };
  }

  searchTerm: string;
  private appList_: App[];
  private appNotificationsObserverReceiver_: AppNotificationsObserverReceiver|
      null;
  private filteredAppList_: App[];
  private mojoInterfaceProvider_: AppNotificationsHandlerInterface;

  constructor() {
    super();

    this.mojoInterfaceProvider_ = getAppNotificationProvider();

    /**
     * Receiver responsible for observing app notification events.
     */
    this.appNotificationsObserverReceiver_ = null;
  }

  override connectedCallback(): void {
    // This page only exists when revamp wayfinding is enabled.
    assert(isRevampWayfindingEnabled());

    super.connectedCallback();

    this.startObservingAppNotifications_();
    this.mojoInterfaceProvider_.getApps().then((result) => {
      this.appList_ = result.apps;
    });
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback();
    this.appNotificationsObserverReceiver_!.$.close();
  }

  private startObservingAppNotifications_(): void {
    this.appNotificationsObserverReceiver_ =
        new AppNotificationsObserverReceiver(this);
    this.mojoInterfaceProvider_.addObserver(
        this.appNotificationsObserverReceiver_.$.bindNewPipeAndPassRemote());
  }

  /** Override ash.settings.appNotification.onQuietModeChanged */
  onQuietModeChanged(): void {
    // Do nothing.
  }

  /** Override ash.settings.appNotification.onNotificationAppChanged */
  onNotificationAppChanged(updatedApp: App): void {
    // Using Polymer mutation methods do not properly handle splice updates with
    // object that have deep properties. Create and assign a copy list instead.
    const appList = Array.from(this.appList_);
    const foundIdx = this.appList_.findIndex(app => {
      return app.id === updatedApp.id;
    });
    if (isAppInstalled(updatedApp)) {
      if (foundIdx !== -1) {
        appList[foundIdx] = updatedApp;
      } else {
        appList.push(updatedApp);
      }
      this.appList_ = appList;
      return;
    }

    // Cannot have an app that is uninstalled prior to being installed.
    assert(foundIdx !== -1);
    // Uninstalled app found, remove it from the list.
    appList.splice(foundIdx, 1);
    this.appList_ = appList;
  }

  private computeFilteredAppList_(): App[] {
    if (this.appList_.length === 0) {
      return [];
    }

    if (!this.searchTerm) {
      return [...this.appList_];
    }

    const lowerCaseSearchTerm = this.searchTerm.toLocaleLowerCase();
    return this.appList_.filter(app => {
      assert(app.title);
      return app.title.toLocaleLowerCase().includes(lowerCaseSearchTerm);
    });
  }

  /**
   * A function used for sorting languages alphabetically.
   */
  private alphabeticalSort_(first: App, second: App): number {
    return first.title!.localeCompare(second.title!);
  }

  private isAppListEmpty_(appList: App[]): boolean {
    return appList.length === 0;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    [SettingsAppNotificationsManagerSubpage.is]:
        SettingsAppNotificationsManagerSubpage;
  }
}

customElements.define(
    SettingsAppNotificationsManagerSubpage.is,
    SettingsAppNotificationsManagerSubpage);