chromium/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/ui_controller.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.

import {TestImportManager} from '/common/testing/test_import_manager.js';

import {LocaleInfo} from './locale_info.js';

import HintType = chrome.accessibilityPrivate.DictationBubbleHintType;
import IconType = chrome.accessibilityPrivate.DictationBubbleIconType;

/** States the Dictation bubble UI can be in. */
/* eslint-disable @typescript-eslint/naming-convention */
export enum UIState {
  STANDBY = 'standby',
  RECOGNIZING_TEXT = 'recognizing_text',
  MACRO_SUCCESS = 'macro_success',
  MACRO_FAIL = 'macro_fail',
  HIDDEN = 'hidden',
}

/** Contexts in which hints can be shown. */
export enum HintContext {
  STANDBY = 'standby',
  TEXT_COMMITTED = 'text_committed',
  TEXT_SELECTED = 'text_selected',
  MACRO_SUCCESS = 'macro_success',
}

interface StateProperties {
  text?: string;
  context?: HintContext;
}

/**
 * Handles interaction with the Dictation UI. All changes to the UI should go
 * this class.
 */
/* eslint-disable @typescript-eslint/naming-convention */
export class UIController {
  private showHintsTimeoutId_: number|null = null;
  private showHintsTimeoutMs_: number =
      UIController.HintsTimeouts.STANDARD_HINT_TIMEOUT_MS_;

  /**
   * Sets the new state of the Dictation bubble UI. If a HintContext is
   * specified, additional hints will appear in the UI after a short timeout.
   */
  setState(state: UIState, properties?: StateProperties): void {
    const props = properties || {};
    const {text, context} = props;
    // Whenever the UI state changes, we should clear the hint timeout.
    this.clearHintsTimeout_();
    switch (state) {
      case UIState.STANDBY:
        chrome.accessibilityPrivate.updateDictationBubble(
            {visible: true, icon: IconType.STANDBY});
        break;
      case UIState.RECOGNIZING_TEXT:
        chrome.accessibilityPrivate.updateDictationBubble(
            {visible: true, icon: IconType.HIDDEN, text});
        break;
      case UIState.MACRO_SUCCESS:
        chrome.accessibilityPrivate.updateDictationBubble(
            {visible: true, icon: IconType.MACRO_SUCCESS, text});
        break;
      case UIState.MACRO_FAIL:
        chrome.accessibilityPrivate.updateDictationBubble(
            {visible: true, icon: IconType.MACRO_FAIL, text});
        break;
      case UIState.HIDDEN:
        chrome.accessibilityPrivate.updateDictationBubble(
            {visible: false, icon: IconType.HIDDEN});
        break;
    }

    if (!context || !LocaleInfo.areCommandsSupported()) {
      // Do not show hints if commands are not supported.
      return;
    }

    // If a HintContext was provided, set a timeout to show hints.
    const hints = UIController.getHintsForContext_(context);
    this.showHintsTimeoutId_ =
        setTimeout(() => this.showHints_(hints), this.showHintsTimeoutMs_);
  }

  private clearHintsTimeout_(): void {
    if (this.showHintsTimeoutId_ !== null) {
      clearTimeout(this.showHintsTimeoutId_);
      this.showHintsTimeoutId_ = null;
    }
  }

  /** Shows hints in the UI bubble. */
  private showHints_(hints: HintType[]): void {
    chrome.accessibilityPrivate.updateDictationBubble(
        {visible: true, icon: IconType.STANDBY, hints});
  }

  /**
   * In some circumstances we shouldn't show the hints too quickly because
   * it is distracting to the user.
   * @param longerDuration Whether to wait for a longer time before showing
   *     hints.
   */
  setHintsTimeoutDuration(longerDuration: boolean): void {
    this.showHintsTimeoutMs_ = longerDuration ?
        UIController.HintsTimeouts.LONGER_HINT_TIMEOUT_MS_ :
        UIController.HintsTimeouts.STANDARD_HINT_TIMEOUT_MS_;
  }

  private static getHintsForContext_(context: HintContext): HintType[] {
    return UIController.CONTEXT_TO_HINTS_MAP_[context];
  }
}

export namespace UIController {
  /** The amount of time to wait before showing hints. */
  export const HintsTimeouts = {
    STANDARD_HINT_TIMEOUT_MS_: 2 * 1000,
    LONGER_HINT_TIMEOUT_MS_: 6 * 1000,
  };

  /** Maps HintContexts to hints that should be shown for that context. */
  export const CONTEXT_TO_HINTS_MAP_ = {
    [HintContext.STANDBY]: [HintType.TRY_SAYING, HintType.TYPE, HintType.HELP],
    [HintContext.TEXT_COMMITTED]:
        [
          HintType.TRY_SAYING,
          HintType.UNDO,
          HintType.DELETE,
          HintType.SELECT_ALL,
          HintType.HELP,
        ],
    [HintContext.TEXT_SELECTED]:
        [
          HintType.TRY_SAYING,
          HintType.UNSELECT,
          HintType.COPY,
          HintType.DELETE,
          HintType.HELP,
        ],
    [HintContext.MACRO_SUCCESS]:
        [HintType.TRY_SAYING, HintType.UNDO, HintType.HELP],
  };
}

TestImportManager.exportForTesting(UIController);