chromium/chrome/browser/resources/nearby_share/app.ts

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

import '/shared/nearby_onboarding_one_page.js';
import '/shared/nearby_onboarding_page.js';
import '/shared/nearby_visibility_page.js';
import './nearby_confirmation_page.js';
import './nearby_discovery_page.js';
import 'chrome://resources/ash/common/cr_elements/cr_view_manager/cr_view_manager.js';

import type {ConfirmationManagerInterface, PayloadPreview, ShareTarget, TransferUpdateListenerPendingReceiver} from '/shared/nearby_share.mojom-webui.js';
import {NearbyShareSettingsMixin} from '/shared/nearby_share_settings_mixin.js';
import {CloseReason} from '/shared/types.js';
import type {CrViewManagerElement} from 'chrome://resources/ash/common/cr_elements/cr_view_manager/cr_view_manager.js';
import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {getTemplate} from './app.html.js';

/**
 * @fileoverview The 'nearby-share' component is the entry point for the Nearby
 * Share flow. It is used as a standalone dialog via chrome://nearby and as part
 * of the ChromeOS share sheet.
 */

enum Page {
  CONFIRMATION = 'confirmation',
  DISCOVERY = 'discovery',
  ONBOARDING = 'onboarding',
  ONEPAGE_ONBOARDING = 'onboarding-one',
  VISIBILITY = 'visibility',
}

const NearbyShareAppElementBase = NearbyShareSettingsMixin(PolymerElement);

export interface NearbyShareAppElement {
  $: {
    viewManager: CrViewManagerElement,
  };
}

export class NearbyShareAppElement extends NearbyShareAppElementBase {
  static get is() {
    return 'nearby-share-app';
  }

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

  static get properties() {
    return {
      /** Mirroring the enum so that it can be used from HTML bindings. */
      Page: {
        type: Object,
        value: Page,
      },

      /**
       * Set by the nearby-discovery-page component when switching to the
       * nearby-confirmation-page.
       */
      confirmationManager_: {
        type: Object,
        value: null,
      },

      /**
       * Set by the nearby-discovery-page component when switching to the
       * nearby-confirmation-page.
       */
      transferUpdateListener_: {
        type: Object,
        value: null,
      },

      /**
       * The currently selected share target set by the nearby-discovery-page
       * component when the user selects a device.
       */
      selectedShareTarget_: {
        type: Object,
        value: null,
      },

      /**
       * Preview info of attachment to be sent, set by the
       * nearby-discovery-page.
       */
      payloadPreview_: {
        type: Object,
        value: null,
      },
    };
  }

  private confirmationManager_: ConfirmationManagerInterface|null;
  private transferUpdateListener_: TransferUpdateListenerPendingReceiver|null;
  private selectedShareTarget_: ShareTarget|null;
  private payloadPreview_: PayloadPreview|null;

  override ready() {
    super.ready();

    this.addEventListener(
        'change-page', e => this.onChangePage_(e as CustomEvent<{page: Page}>));
    this.addEventListener(
        'close', e => this.onClose_(e as CustomEvent<{reason: CloseReason}>));
    this.addEventListener('onboarding-complete', this.onOnboardingComplete_);

    ColorChangeUpdater.forDocument().start();
  }

  /**
   * Called whenever view changes.
   * ChromeVox screen reader requires focus on #pageContainer to read
   * dialog.
   */
  private focusOnPageContainer_(page: string) {
    this.shadowRoot!.querySelector(`nearby-${
        page}-page`)!.shadowRoot!.querySelector('nearby-page-template')!
        .shadowRoot!.querySelector<HTMLElement>('#pageContainer')!.focus();
  }

  /**
   * Determines if the feature flag for One-page onboarding workflow is enabled.
   * @return Whether the one-page onboarding is enabled
   */
  private isOnePageOnboardingEnabled_(): boolean {
    return loadTimeData.getBoolean('isOnePageOnboardingEnabled');
  }

  /**
   * Called when component is attached and all settings values have been
   * retrieved.
   */
  override onSettingsRetrieved() {
    if (this.settings.isOnboardingComplete) {
      if (!this.settings.enabled) {
        // When a new share is triggered, if the user has completed onboarding
        // previously, then silently enable the feature and continue to
        // discovery page directly.
        this.set('settings.enabled', true);
      }
      this.$.viewManager.switchView(Page.DISCOVERY);
      this.focusOnPageContainer_(Page.DISCOVERY);

      return;
    }

    const onboardingPage = this.isOnePageOnboardingEnabled_() ?
        Page.ONEPAGE_ONBOARDING :
        Page.ONBOARDING;
    this.$.viewManager.switchView(onboardingPage);
    this.focusOnPageContainer_(onboardingPage);
  }

  /**
   * Handler for the change-page event.
   */
  private onChangePage_(event: CustomEvent<{page: Page}>) {
    this.$.viewManager.switchView(event.detail.page);
    this.focusOnPageContainer_(event.detail.page);
  }

  /**
   * Handler for the close event.
   */
  private onClose_(event: CustomEvent<{reason: CloseReason}>) {
    // TODO(b/237796007): Handle the case of null |event.detail|
    const reason =
        event.detail.reason == null ? CloseReason.UNKNOWN : event.detail.reason;
    chrome.send('close', [reason]);
  }

  /**
   * Handler for when onboarding is completed.
   */
  private onOnboardingComplete_() {
    this.$.viewManager.switchView(Page.DISCOVERY);
    this.focusOnPageContainer_(Page.DISCOVERY);
  }
}

customElements.define(NearbyShareAppElement.is, NearbyShareAppElement);