chromium/ash/webui/personalization_app/resources/js/theme/personalization_theme_element.ts

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

/**
 * @fileoverview This component displays color mode settings.
 */

import 'chrome://resources/ash/common/personalization/common.css.js';
import 'chrome://resources/ash/common/personalization/cros_button_style.css.js';
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
import 'chrome://resources/ash/common/cr_elements/localized_link/localized_link.js';
import 'chrome://resources/ash/common/cr_elements/policy/cr_tooltip_icon.js';
import '../geolocation_dialog.js';

import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {IronA11yKeysElement} from 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';

import {isCrosPrivacyHubLocationEnabled} from '../load_time_booleans.js';
import {WithPersonalizationStore} from '../personalization_store.js';
import {isSelectionEvent} from '../utils.js';

import {getTemplate} from './personalization_theme_element.html.js';
import {enableGeolocationForSystemServices, initializeData, setColorModeAutoSchedule, setColorModePref} from './theme_controller.js';
import {getThemeProvider} from './theme_interface_provider.js';
import {ThemeObserver} from './theme_observer.js';

export interface PersonalizationThemeElement {
  $: {
    keys: IronA11yKeysElement,
    selector: IronSelectorElement,
  };
}

export class PersonalizationThemeElement extends WithPersonalizationStore {
  static get is() {
    return 'personalization-theme';
  }

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

  static get properties() {
    return {
      // null indicates value is being loaded.
      darkModeEnabled_: {
        type: Boolean,
        value: null,
      },

      colorModeAutoScheduleEnabled_: {
        type: Boolean,
        value: null,
      },

      geolocationPermissionEnabled_: {
        type: Boolean,
        value: null,
      },

      sunriseTime_: {
        type: String,
        value: null,
      },

      sunsetTime_: {
        type: String,
        value: null,
      },

      /** The button currently highlighted by keyboard navigation. */
      selectedButton_: {
        type: Object,
        notify: true,
      },

      shouldShowGeolocationDialog_: {
        type: Boolean,
        value: false,
      },
    };
  }

  private darkModeEnabled_: boolean|null;
  private colorModeAutoScheduleEnabled_: boolean|null;
  private geolocationPermissionEnabled_: boolean|null;
  private sunriseTime_: string|null;
  private sunsetTime_: string|null;
  private selectedButton_: CrButtonElement;
  private shouldShowGeolocationDialog_: boolean;
  private shouldShowGeolocationWarningText_: boolean;

  override ready() {
    super.ready();
    this.$.keys.target = this.$.selector;
  }

  override connectedCallback() {
    super.connectedCallback();
    ThemeObserver.initThemeObserverIfNeeded();
    this.watch<PersonalizationThemeElement['darkModeEnabled_']>(
        'darkModeEnabled_', state => state.theme.darkModeEnabled);
    this.watch<PersonalizationThemeElement['colorModeAutoScheduleEnabled_']>(
        'colorModeAutoScheduleEnabled_',
        state => state.theme.colorModeAutoScheduleEnabled);
    this.watch<PersonalizationThemeElement['geolocationPermissionEnabled_']>(
        'geolocationPermissionEnabled_',
        state => state.theme.geolocationPermissionEnabled);
    this.watch<PersonalizationThemeElement['sunriseTime_']>(
        'sunriseTime_', state => state.theme.sunriseTime);
    this.watch<PersonalizationThemeElement['sunsetTime_']>(
        'sunsetTime_', state => state.theme.sunsetTime);

    this.updateFromStore();
    initializeData(getThemeProvider(), this.getStore());
  }

  /** Handle keyboard navigation. */
  private onKeysPress_(
      e: CustomEvent<{key: string, keyboardEvent: KeyboardEvent}>) {
    const selector = this.$.selector;
    const prevButton = this.selectedButton_;
    switch (e.detail.key) {
      case 'left':
        selector.selectPrevious();
        break;
      case 'right':
        selector.selectNext();
        break;
      default:
        return;
    }
    // Remove focus state of previous button.
    if (prevButton) {
      prevButton.removeAttribute('tabindex');
    }
    // Add focus state for new button.
    if (this.selectedButton_) {
      this.selectedButton_.setAttribute('tabindex', '0');
      this.selectedButton_.focus();
    }
    e.detail.keyboardEvent.preventDefault();
  }

  private getLightAriaChecked_(): string {
    //  If auto schedule mode is enabled, the system disregards whether dark
    //  mode is enabled or not. To ensure expected behavior, we show that dark
    //  mode can only be selected only when auto schedule mode is disabled and
    //  dark mode is enabled. Also explictly check that these prefs are not null
    //  to avoid showing this button as selected when the values are being
    //  fetched.
    return (this.colorModeAutoScheduleEnabled_ !== null &&
            this.darkModeEnabled_ !== null &&
            !this.colorModeAutoScheduleEnabled_ && !this.darkModeEnabled_)
        .toString();
  }

  private getDarkAriaChecked_(): string {
    //  If auto schedule mode is enabled, the system disregards whether dark
    //  mode is enabled or not. To ensure expected behavior, we show that light
    //  mode is selected only when both auto schedule mode and dark mode are
    //  disabled.
    return (!this.colorModeAutoScheduleEnabled_ && !!this.darkModeEnabled_)
        .toString();
  }

  private getAutoAriaChecked_(): string {
    return (!!this.colorModeAutoScheduleEnabled_).toString();
  }

  private onClickColorModeButton_(event: Event) {
    if (!isSelectionEvent(event)) {
      return;
    }

    const eventTarget = event.currentTarget as HTMLElement;
    const colorMode = eventTarget.dataset['colorMode'];
    // Disables auto schedule mode if a specific color mode is set.
    setColorModeAutoSchedule(
        /*colorModeAutoScheduleEnabled=*/ false, getThemeProvider(),
        this.getStore());
    setColorModePref(colorMode === 'DARK', getThemeProvider(), this.getStore());
  }

  private onClickAutoModeButton_(event: Event) {
    if (!isSelectionEvent(event) || this.colorModeAutoScheduleEnabled_) {
      return;
    }
    setColorModeAutoSchedule(
        /*enabled=*/ true, getThemeProvider(), this.getStore());

    // If needed, pop up a dialog asking users to enable system location
    // permission.
    if (isCrosPrivacyHubLocationEnabled() &&
        this.geolocationPermissionEnabled_ === false) {
      this.shouldShowGeolocationDialog_ = true;
    }
  }

  private computeShouldShowTooltipIcon_(): boolean {
    return isCrosPrivacyHubLocationEnabled() &&
        this.colorModeAutoScheduleEnabled_ === true &&
        this.geolocationPermissionEnabled_ === false;
  }

  private computeAutoModeGeolocationDialogText_(): string {
    return loadTimeData.getStringF(
        'autoModeGeolocationDialogText', this.sunriseTime_!, this.sunsetTime_!);
  }

  private onGeolocationDialogClose_(): void {
    this.shouldShowGeolocationDialog_ = false;
  }

  // Callback for user clicking 'Allow' on the geolocation dialog.
  private onGeolocationEnabled_(): void {
    // Enable system geolocation permission for all system services.
    enableGeolocationForSystemServices(getThemeProvider(), this.getStore());
  }
}

customElements.define(
    PersonalizationThemeElement.is, PersonalizationThemeElement);