chromium/chrome/browser/resources/ash/settings/device_page/display_night_light.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
 * 'settings-display-night-light' is the section on the display subpage
 * for display night light preferences' adjustments.
 */

import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import '../settings_scheduler_slider/settings_scheduler_slider.js';
import '../settings_shared.css.js';
import '../settings_vars.css.js';
import '../controls/settings_slider.js';
import '../controls/settings_dropdown_menu.js';
import '../controls/settings_toggle_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_slider/cr_slider.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_style.css.js';

import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.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 {DeepLinkingMixin} from '../common/deep_linking_mixin.js';
import {DisplaySettingsNightLightScheduleOption, DisplaySettingsProviderInterface, DisplaySettingsType} from '../mojom-webui/display_settings_provider.mojom-webui.js';
import {Setting} from '../mojom-webui/setting.mojom-webui.js';
import {PrivacyHubBrowserProxy, PrivacyHubBrowserProxyImpl} from '../os_privacy_page/privacy_hub_browser_proxy.js';
import {GeolocationAccessLevel} from '../os_privacy_page/privacy_hub_geolocation_subpage.js';

import {getTemplate} from './display_night_light.html.js';
import {getDisplaySettingsProvider} from './display_settings_mojo_interface_provider.js';

/**
 * The types of Night Light automatic schedule. The values of the enum values
 * are synced with the pref "prefs.ash.night_light.schedule_type".
 */
export enum NightLightScheduleType {
  NEVER = 0,
  SUNSET_TO_SUNRISE = 1,
  CUSTOM = 2,
}

/**
 * Required member fields for events which select displays.
 */
interface ScheduleType {
  name: string;
  value: NightLightScheduleType;
}

const SettingsDisplayNightLightElementBase =
    DeepLinkingMixin(PrefsMixin(I18nMixin(PolymerElement)));

export class SettingsDisplayNightLightElement extends
    SettingsDisplayNightLightElementBase {
  static get is() {
    return 'settings-display-night-light' as const;
  }

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

  static get properties() {
    return {
      scheduleTypesList_: {
        type: Array,
        value() {
          return [
            {
              name: loadTimeData.getString('displayNightLightScheduleNever'),
              value: NightLightScheduleType.NEVER,
            },
            {
              name: loadTimeData.getString(
                  'displayNightLightScheduleSunsetToSunRise'),
              value: NightLightScheduleType.SUNSET_TO_SUNRISE,
            },
            {
              name: loadTimeData.getString('displayNightLightScheduleCustom'),
              value: NightLightScheduleType.CUSTOM,
            },
          ];
        },
      },

      shouldOpenCustomScheduleCollapse_: {
        type: Boolean,
        value: false,
      },

      nightLightScheduleSubLabel_: String,

      /**
       * Used by DeepLinkingMixin to focus this page's deep links.
       */
      supportedSettingIds: {
        type: Object,
        value: () => new Set<Setting>([
          Setting.kNightLight,
          Setting.kNightLightColorTemperature,
        ]),
      },

      shouldShowGeolocationWarningText_: {
        type: Boolean,
        computed: 'computeShouldShowGeolocationWarningText_(' +
            'prefs.ash.night_light.schedule_type.value, ' +
            'prefs.ash.user.geolocation_access_level.value),',
      },

      sunriseTime_: {
        type: String,
        value() {
          return loadTimeData.getString(
              'privacyHubSystemServicesInitSunRiseTime');
        },
      },

      sunsetTime_: {
        type: String,
        value() {
          return loadTimeData.getString(
              'privacyHubSystemServicesInitSunSetTime');
        },
      },

      geolocationWarningText_: {
        type: String,
        computed: 'computeGeolocationWarningText_(' +
            'prefs.ash.user.geolocation_access_level.*,' +
            'sunriseTime_, sunsetTime_)',

      },

      shouldShowEnableGeolocationDialog_: {
        type: Boolean,
        value: false,
      },

      isInternalDisplay: Boolean,

      /**
       * Current status of night light setting.
       */
      currentNightLightStatus: Boolean,

      /**
       * Current selected night light schedule type.
       */
      currentScheduleType: NightLightScheduleType,
    };
  }

  static get observers() {
    return [
      'updateNightLightScheduleSettings_(prefs.ash.night_light.schedule_type.*,' +
          ' prefs.ash.night_light.enabled.*),',
      'onTimeZoneChanged_(prefs.cros.system.timezone.value)',
    ];
  }

  isInternalDisplay: boolean;
  private displaySettingsProvider: DisplaySettingsProviderInterface =
      getDisplaySettingsProvider();
  private nightLightScheduleSubLabel_: string;
  private scheduleTypesList_: ScheduleType[];
  private shouldOpenCustomScheduleCollapse_: boolean;
  private shouldShowGeolocationDialog_: boolean;
  private shouldShowGeolocationWarningText_: boolean;
  private currentNightLightStatus: boolean;
  private currentScheduleType: NightLightScheduleType;
  private sunriseTime_: string;
  private sunsetTime_: string;
  private privacyHubBrowserProxy_: PrivacyHubBrowserProxy;

  constructor() {
    super();
    this.privacyHubBrowserProxy_ = PrivacyHubBrowserProxyImpl.getInstance();
  }
  /**
   * Invoked when the status of Night Light or its schedule type are changed,
   * in order to update the schedule settings, such as whether to show the
   * custom schedule slider, and the schedule sub label.
   */
  private updateNightLightScheduleSettings_(): void {
    const scheduleType = this.getPref('ash.night_light.schedule_type').value;
    this.shouldOpenCustomScheduleCollapse_ =
        scheduleType === NightLightScheduleType.CUSTOM;

    const nightLightStatus: boolean =
        this.getPref('ash.night_light.enabled').value;
    if (scheduleType === NightLightScheduleType.SUNSET_TO_SUNRISE) {
      this.nightLightScheduleSubLabel_ = nightLightStatus ?
          this.i18n('displayNightLightOffAtSunrise') :
          this.i18n('displayNightLightOnAtSunset');
    } else {
      this.nightLightScheduleSubLabel_ = '';
    }

    // Records metrics when schedule type or night light status have changed. Do
    // not record when the page just loads and the current value is still
    // undefined.
    if (this.currentScheduleType !== scheduleType &&
        this.currentScheduleType !== undefined) {
      this.recordChangingNightLightSchedule(
          this.isInternalDisplay, scheduleType);
    }
    if (this.currentNightLightStatus !== nightLightStatus &&
        this.currentNightLightStatus !== undefined) {
      this.recordTogglingNightLightStatus(
          this.isInternalDisplay, nightLightStatus);
    }

    // Updates current schedule type and night light status.
    this.currentScheduleType = scheduleType;
    this.currentNightLightStatus = nightLightStatus;
  }

  // Records metrics when users change the night light schedule.
  private recordChangingNightLightSchedule(
      isInternalDisplay: boolean,
      nightLightSchedule: DisplaySettingsNightLightScheduleOption): void {
    this.displaySettingsProvider.recordChangingDisplaySettings(
        DisplaySettingsType.kNightLightSchedule, {
          isInternalDisplay,
          nightLightSchedule,
          displayId: null,
          orientation: null,
          nightLightStatus: null,
          mirrorModeStatus: null,
          unifiedModeStatus: null,
        });
  }

  // Records metrics when users toggle the night light status.
  private recordTogglingNightLightStatus(
      isInternalDisplay: boolean, nightLightStatus: boolean): void {
    this.displaySettingsProvider.recordChangingDisplaySettings(
        DisplaySettingsType.kNightLight, {
          isInternalDisplay,
          nightLightStatus,
          displayId: null,
          orientation: null,
          nightLightSchedule: null,
          mirrorModeStatus: null,
          unifiedModeStatus: null,
        });
  }

  private computeShouldShowGeolocationWarningText_(): boolean {
    const scheduleType = this.prefs.ash.night_light.schedule_type.value;
    const geolocationAccessLevel =
        this.prefs.ash.user.geolocation_access_level.value;

    return (
        scheduleType === NightLightScheduleType.SUNSET_TO_SUNRISE &&
        geolocationAccessLevel === GeolocationAccessLevel.DISALLOWED);
  }

  private computeGeolocationWarningText_(): string {
    if (!this.prefs) {
      return '';
    }

    if (this.prefs.ash.user.geolocation_access_level.enforcement ===
        chrome.settingsPrivate.Enforcement.ENFORCED) {
      return loadTimeData.getStringF(
          'displayNightLightGeolocationManagedWarningText', this.sunriseTime_,
          this.sunsetTime_);
    } else {
      return loadTimeData.getStringF(
          'displayNightLightGeolocationWarningText', this.sunriseTime_,
          this.sunsetTime_);
    }
  }

  private openGeolocationDialog_(): void {
    this.shouldShowGeolocationDialog_ = true;
  }

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

  private onTimeZoneChanged_(): void {
    this.privacyHubBrowserProxy_.getCurrentSunriseTime().then((time) => {
      this.sunriseTime_ = time;
    });
    this.privacyHubBrowserProxy_.getCurrentSunsetTime().then((time) => {
      this.sunsetTime_ = time;
    });
  }
}

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

customElements.define(
    SettingsDisplayNightLightElement.is, SettingsDisplayNightLightElement);