chromium/chrome/browser/resources/ash/settings/device_page/per_device_subsection_header.ts

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

/**
 * @fileoverview
 * 'per-device-subsection-header' displays information about a device, with
 * conditional layout based on the 'isWelcomeExperienceEnabled' flag.
 * - When enabled: Shows device image (if available), name, and optional battery
 * info.
 * - When disabled: Shows device name only.
 */

import './input_device_settings_shared.css.js';
import '../icons.html.js';
import '../os_settings_icons.html.js';
import '../settings_shared.css.js';
import 'chrome://resources/ash/common/bluetooth/bluetooth_battery_icon_percentage.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';

import {BatteryType} from 'chrome://resources/ash/common/bluetooth/bluetooth_types.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {BluetoothDeviceProperties} from 'chrome://resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {getInputDeviceSettingsProvider} from './input_device_mojo_interface_provider.js';
import {BatteryInfo, InputDeviceSettingsProviderInterface} from './input_device_settings_types.js';
import {createBluetoothDeviceProperties} from './input_device_settings_utils.js';
import {getTemplate} from './per_device_subsection_header.html.js';

// Defines the possible states for a device's display.
enum DeviceDisplayState {
  FETCHING_IMAGE = 0,
  IMAGE_AVAILABLE = 1,
  IMAGE_UNAVAILABLE = 2,
}

const PerDeviceSubsectionHeaderElementBase = I18nMixin(PolymerElement);

export class PerDeviceSubsectionHeaderElement extends
    PerDeviceSubsectionHeaderElementBase {
  static get is() {
    return 'per-device-subsection-header' as const;
  }

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

  static get properties(): PolymerElementProperties {
    return {
      isWelcomeExperienceEnabled: {
        type: Boolean,
        value() {
          return loadTimeData.getBoolean('enableWelcomeExperience');
        },
        readOnly: true,
      },

      deviceImageDataUrl: {
        type: String,
        value: '',
      },

      batteryInfo: {
        type: Object,
      },

      bluetoothDevice: {
        type: Object,
        computed: 'computeBluetoothDeviceProperties(batteryInfo.*)',
      },

      deviceKey: {
        type: String,
      },

      name: {
        type: String,
      },

      icon: {
        type: String,
      },

      deviceDisplayState: {
        type: Number,
        value: DeviceDisplayState.FETCHING_IMAGE,
      },
    };
  }

  isWelcomeExperienceEnabled: boolean;
  deviceImageDataUrl: string|null = null;
  deviceKey: string;
  deviceDisplayState: DeviceDisplayState;
  name: string;
  bluetoothDevice: BluetoothDeviceProperties|null;
  batteryInfo: BatteryInfo|null;
  icon: string;
  private inputDeviceSettingsProvider: InputDeviceSettingsProviderInterface =
      getInputDeviceSettingsProvider();

  static get observers() {
    return [
      'handleDeviceKeyChange(deviceKey)',
    ];
  }

  showBatteryInfo(): boolean {
    return !!this.batteryInfo;
  }

  getDefaultBatteryType(): BatteryType {
    return BatteryType.DEFAULT;
  }

  computeBluetoothDeviceProperties(): BluetoothDeviceProperties|null {
    if (!this.batteryInfo) {
      return null;
    }

    return createBluetoothDeviceProperties(
        this.deviceKey, this.name, this?.batteryInfo.batteryPercentage);
  }

  async handleDeviceKeyChange(): Promise<void> {
    if (this.isWelcomeExperienceEnabled) {
      this.deviceDisplayState = DeviceDisplayState.FETCHING_IMAGE;
      this.deviceImageDataUrl =
          (await this.inputDeviceSettingsProvider.getDeviceIconImage(
               this.deviceKey))
              ?.dataUrl;
      this.deviceDisplayState = this.deviceImageDataUrl ?
          DeviceDisplayState.IMAGE_AVAILABLE :
          DeviceDisplayState.IMAGE_UNAVAILABLE;
    }
  }

  shouldShowPlaceholder(): boolean {
    return this.deviceDisplayState === DeviceDisplayState.FETCHING_IMAGE;
  }

  shouldShowDeviceIcon(): boolean {
    return this.deviceDisplayState === DeviceDisplayState.IMAGE_UNAVAILABLE;
  }

  getAriaLabel(): string {
    let label = `${this.i18n('deviceNameLabel')} ${this.name}`;
    if (this.batteryInfo?.batteryPercentage) {
      label += ` ${
          this.i18n(
              'deviceBatteryLabel', this?.batteryInfo.batteryPercentage)}`;
    }
    return label;
  }
}

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

customElements.define(
    PerDeviceSubsectionHeaderElement.is, PerDeviceSubsectionHeaderElement);