Commit 581f0c7e authored by My Nguyen's avatar My Nguyen Committed by Commit Bot

[OsSettingsLanguages] Add all input methods and search UI

Mock: http://go/cros-lang-settings-ux-slide#slide=24
Figma: http://go/cros-lang-settings-figma
Normal view: http://screen/BSw5Hr7mBSADReH
Search view: http://screen/6VKAdL8xeyzJvCS

Note: Input methods names look like duplicate now because they are under
revision. Will add capability to search by language and country later.

Bug: 1113439
Change-Id: Id28cd18db110321048be54ce8c0948ae9a76fa8e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2397988
Commit-Queue: My Nguyen <myy@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarRegan Hsu <hsuregan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806933}
parent de2afca6
...@@ -304,6 +304,9 @@ ...@@ -304,6 +304,9 @@
<message name="IDS_OS_SETTINGS_LANGUAGES_ALL_INPUT_METHODS_LABEL" translateable="false" desc="Title of the list of all keyboard layouts or input method editors."> <message name="IDS_OS_SETTINGS_LANGUAGES_ALL_INPUT_METHODS_LABEL" translateable="false" desc="Title of the list of all keyboard layouts or input method editors.">
All input methods All input methods
</message> </message>
<message name="IDS_OS_SETTINGS_LANGUAGES_SEARCH_INPUT_METHODS_LABEL" translateable="false" desc="Placeholder in the input field to search keyboard layouts or input method editors.">
Search input methods
</message>
<message name="IDS_OS_SETTINGS_LANGUAGES_SPELL_CHECK_TITLE" translateable="false" desc="Title for the section containing all the options for spell check settings. The options include picking between using the system's spell check or using Google's web service and a list of the enabled languages which support spell checking."> <message name="IDS_OS_SETTINGS_LANGUAGES_SPELL_CHECK_TITLE" translateable="false" desc="Title for the section containing all the options for spell check settings. The options include picking between using the system's spell check or using Google's web service and a list of the enabled languages which support spell checking.">
Spell check Spell check
</message> </message>
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/ime/chromeos/component_extension_ime_manager.h" #include "ui/base/ime/chromeos/component_extension_ime_manager.h"
#include "ui/base/ime/chromeos/extension_ime_util.h" #include "ui/base/ime/chromeos/extension_ime_util.h"
#include "ui/base/ime/chromeos/input_method_manager.h" #include "ui/base/ime/chromeos/input_method_manager.h"
...@@ -133,6 +134,12 @@ std::vector<std::string> GetSortedComponentIMEs( ...@@ -133,6 +134,12 @@ std::vector<std::string> GetSortedComponentIMEs(
} }
} }
} }
if (base::FeatureList::IsEnabled(
chromeos::features::kLanguageSettingsUpdate)) {
for (const auto& input_method_id : available_component_imes) {
component_ime_list.push_back(input_method_id);
}
}
return component_ime_list; return component_ime_list;
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
<link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html"> <link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html">
<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html">
<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
<link rel="import" href="../metrics_recorder.html"> <link rel="import" href="../metrics_recorder.html">
...@@ -19,6 +20,15 @@ ...@@ -19,6 +20,15 @@
padding-inline-end: 0; padding-inline-end: 0;
} }
cr-search-field {
padding-bottom: 8px;
}
.label {
padding-bottom: 4px;
padding-top: 8px;
}
.list-item { .list-item {
color: var(--cros-text-color-primary); color: var(--cros-text-color-primary);
min-height: 36px; min-height: 36px;
...@@ -32,13 +42,20 @@ ...@@ -32,13 +42,20 @@
<cr-dialog id="dialog" close-text="$i18n{close}" show-on-attach> <cr-dialog id="dialog" close-text="$i18n{close}" show-on-attach>
<div slot="title">$i18n{addInputMethodLabel}</div> <div slot="title">$i18n{addInputMethodLabel}</div>
<div id="dialogBody" slot="body" scrollable> <div id="dialogBody" slot="body" scrollable>
<cr-search-field label="$i18n{searchInputMethodsLabel}"
id="search" on-search-changed="onSearchChanged_"
on-keydown="onKeydown_" autofocus>
</cr-search-field>
<template is="dom-if" if="[[showSuggestedList_]]"> <template is="dom-if" if="[[showSuggestedList_]]">
<div id="suggestedInputMethods"> <div id="suggestedInputMethods">
$i18n{suggestedInputMethodsLabel} <div class="label">
$i18n{suggestedInputMethodsLabel}
</div>
<iron-list scroll-target="[[$$('suggestedInputMethods')]]" <iron-list scroll-target="[[$$('suggestedInputMethods')]]"
items="[[suggestedInputMethods_]]"> items="[[suggestedInputMethods_]]">
<template> <template>
<cr-checkbox class="list-item no-outline" <cr-checkbox class="list-item no-outline"
checked="[[willAdd_(item.id, inputMethodsToAdd_.size)]]"
tab-index="[[tabIndex]]" on-change="onCheckboxChange_"> tab-index="[[tabIndex]]" on-change="onCheckboxChange_">
[[item.displayName]] [[item.displayName]]
</cr-checkbox> </cr-checkbox>
...@@ -46,6 +63,23 @@ ...@@ -46,6 +63,23 @@
</iron-list> </iron-list>
</div> </div>
</template> </template>
<div id="allInputMethods">
<div id="allInputMethodsLabel" class="label"
hidden="[[!showSuggestedList_]]">
$i18n{allInputMethodsLabel}
</div>
<iron-list scroll-target="[[$$('allInputMethods')]]"
items="[[getAllInputMethods_(languages.inputMethods,
lowercaseQueryString_)]]">
<template>
<cr-checkbox class="list-item no-outline"
checked="[[willAdd_(item.id, inputMethodsToAdd_.size)]]"
tab-index="[[tabIndex]]" on-change="onCheckboxChange_">
[[item.displayName]]
</cr-checkbox>
</template>
</iron-list>
</div>
</div> </div>
<div slot="button-container"> <div slot="button-container">
<cr-button class="cancel-button" on-click="onCancelButtonTap_"> <cr-button class="cancel-button" on-click="onCancelButtonTap_">
......
...@@ -40,7 +40,8 @@ Polymer({ ...@@ -40,7 +40,8 @@ Polymer({
showSuggestedList_: { showSuggestedList_: {
type: Boolean, type: Boolean,
value: false, value: false,
computed: 'shouldShowSuggestedList_(suggestedInputMethods_)' computed:
'shouldShowSuggestedList_(suggestedInputMethods_, lowercaseQueryString_)'
}, },
/** @private */ /** @private */
...@@ -49,6 +50,20 @@ Polymer({ ...@@ -49,6 +50,20 @@ Polymer({
value: true, value: true,
computed: 'shouldDisableActionButton_(inputMethodsToAdd_.size)', computed: 'shouldDisableActionButton_(inputMethodsToAdd_.size)',
}, },
/** @private */
lowercaseQueryString_: {
type: String,
value: '',
},
},
/**
* @param {!CustomEvent<string>} e
* @private
*/
onSearchChanged_(e) {
this.lowercaseQueryString_ = e.detail.toLocaleLowerCase();
}, },
/** /**
...@@ -67,12 +82,41 @@ Polymer({ ...@@ -67,12 +82,41 @@ Polymer({
}); });
}, },
/**
* @return {!Array<!chrome.languageSettingsPrivate.InputMethod>} A list of
* possible input methods.
* @private
*/
getAllInputMethods_() {
return this.languages.inputMethods.supported.filter(inputMethod => {
// Don't show input methods which are already enabled.
if (this.languageHelper.isInputMethodEnabled(inputMethod.id)) {
return false;
}
// Show input methods whose name matches the query.
return inputMethod.displayName.toLocaleLowerCase().includes(
this.lowercaseQueryString_);
});
},
/** /**
* @return {boolean} * @return {boolean}
* @private * @private
*/ */
shouldShowSuggestedList_() { shouldShowSuggestedList_() {
return this.suggestedInputMethods_.length > 0; return this.suggestedInputMethods_.length > 0 &&
!this.lowercaseQueryString_;
},
/**
* True if the user has chosen to add this input method (checked its
* checkbox).
* @param {string} id
* @return {boolean}
* @private
*/
willAdd_(id) {
return this.inputMethodsToAdd_.has(id);
}, },
/** /**
...@@ -116,4 +160,17 @@ Polymer({ ...@@ -116,4 +160,17 @@ Polymer({
settings.recordSettingChange(); settings.recordSettingChange();
this.$.dialog.close(); this.$.dialog.close();
}, },
/**
* @param {!KeyboardEvent} e
* @private
*/
onKeydown_(e) {
// Close dialog if 'esc' is pressed and the search box is already empty.
if (e.key === 'Escape' && !this.$.search.getValue().trim()) {
this.$.dialog.close();
} else if (e.key !== 'PageDown' && e.key !== 'PageUp') {
this.$.search.scrollIntoViewIfNeeded();
}
},
}); });
...@@ -132,7 +132,7 @@ Polymer({ ...@@ -132,7 +132,7 @@ Polymer({
}, },
/** /**
* Hash set of input method ids that are enabled. * Hash set of enabled input methods id for mebership testings
* @private {!Set<string>} * @private {!Set<string>}
*/ */
enabledInputMethodSet_: { enabledInputMethodSet_: {
...@@ -1090,11 +1090,9 @@ Polymer({ ...@@ -1090,11 +1090,9 @@ Polymer({
const combinedInputMethods = []; const combinedInputMethods = [];
for (const languageCode of languageCodes) { for (const languageCode of languageCodes) {
const inputMethods = this.getInputMethodsForLanguage(languageCode); const inputMethods = this.getInputMethodsForLanguage(languageCode);
// Get the language's unused input methods. // Get the language's unused input methods and mark them as used.
const newInputMethods = inputMethods.filter( const newInputMethods = inputMethods.filter(
inputMethod => !usedInputMethods.has(inputMethod.id)); inputMethod => !usedInputMethods.has(inputMethod.id));
// Some input methods might be used by different languages, save the new
// ones in usedInputMethods to prevent adding them twice.
newInputMethods.forEach( newInputMethods.forEach(
inputMethod => usedInputMethods.add(inputMethod.id)); inputMethod => usedInputMethods.add(inputMethod.id));
combinedInputMethods.push(...newInputMethods); combinedInputMethods.push(...newInputMethods);
......
...@@ -307,6 +307,8 @@ void AddInputPageStringsV2(content::WebUIDataSource* html_source) { ...@@ -307,6 +307,8 @@ void AddInputPageStringsV2(content::WebUIDataSource* html_source) {
IDS_OS_SETTINGS_LANGUAGES_SUGGESTED_INPUT_METHODS_LABEL}, IDS_OS_SETTINGS_LANGUAGES_SUGGESTED_INPUT_METHODS_LABEL},
{"allInputMethodsLabel", {"allInputMethodsLabel",
IDS_OS_SETTINGS_LANGUAGES_ALL_INPUT_METHODS_LABEL}, IDS_OS_SETTINGS_LANGUAGES_ALL_INPUT_METHODS_LABEL},
{"searchInputMethodsLabel",
IDS_OS_SETTINGS_LANGUAGES_SEARCH_INPUT_METHODS_LABEL},
{"spellCheckTitle", IDS_OS_SETTINGS_LANGUAGES_SPELL_CHECK_TITLE}, {"spellCheckTitle", IDS_OS_SETTINGS_LANGUAGES_SPELL_CHECK_TITLE},
{"spellCheckEnhancedLabel", {"spellCheckEnhancedLabel",
IDS_OS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_LABEL}, IDS_OS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_LABEL},
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// clang-format off // clang-format off
// #import {LanguagesBrowserProxyImpl, LanguagesMetricsProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js'; // #import {LanguagesBrowserProxyImpl, LanguagesMetricsProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js';
// #import {CrSettingsPrefs, Router, routes} from 'chrome://os-settings/chromeos/os_settings.js'; // #import {CrSettingsPrefs, Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
// #import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; // #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
// #import {getFakeLanguagePrefs} from '../fake_language_settings_private.m.js' // #import {getFakeLanguagePrefs} from '../fake_language_settings_private.m.js'
// #import {FakeSettingsPrivate} from '../fake_settings_private.m.js'; // #import {FakeSettingsPrivate} from '../fake_settings_private.m.js';
...@@ -169,6 +170,7 @@ suite('input page', () => { ...@@ -169,6 +170,7 @@ suite('input page', () => {
suite('add input methods dialog', () => { suite('add input methods dialog', () => {
let dialog; let dialog;
let suggestedInputMethods; let suggestedInputMethods;
let allInputMethods;
let cancelButton; let cancelButton;
let actionButton; let actionButton;
...@@ -188,6 +190,9 @@ suite('input page', () => { ...@@ -188,6 +190,9 @@ suite('input page', () => {
suggestedInputMethods = dialog.$$('#suggestedInputMethods'); suggestedInputMethods = dialog.$$('#suggestedInputMethods');
assertTrue(!!suggestedInputMethods); assertTrue(!!suggestedInputMethods);
allInputMethods = dialog.$$('#allInputMethods');
assertTrue(!!allInputMethods);
// No input methods has been selected, so the action button is disabled. // No input methods has been selected, so the action button is disabled.
assertTrue(actionButton.disabled); assertTrue(actionButton.disabled);
assertFalse(cancelButton.disabled); assertFalse(cancelButton.disabled);
...@@ -205,19 +210,36 @@ suite('input page', () => { ...@@ -205,19 +210,36 @@ suite('input page', () => {
}); });
test('adds input methods', () => { test('adds input methods', () => {
const listItems = suggestedInputMethods.querySelectorAll('.list-item'); const suggestedItems =
assertEquals(2, listItems.length); suggestedInputMethods.querySelectorAll('.list-item');
assertEquals('US Swahili keyboard', listItems[0].textContent.trim()); // input methods are based on and ordered by enabled languages
assertEquals('Swahili keyboard', listItems[1].textContent.trim()); assertEquals(2, suggestedItems.length);
// selecting two input methods. assertEquals('US Swahili keyboard', suggestedItems[0].textContent.trim());
listItems[0].click(); assertEquals('Swahili keyboard', suggestedItems[1].textContent.trim());
listItems[1].click(); // selecting Swahili keyboard.
suggestedItems[1].click();
const allItems = allInputMethods.querySelectorAll('.list-item');
// All input methods should appear and ordered based on fake settings
// data.
assertEquals(3, allItems.length);
assertEquals('Swahili keyboard', allItems[0].textContent.trim());
// checked is reflected
assertTrue(allItems[0].checked);
assertEquals('US Swahili keyboard', allItems[1].textContent.trim());
assertFalse(allItems[1].checked);
assertEquals('Vietnamese keyboard', allItems[2].textContent.trim());
// selecting Vietnamese keyboard
allItems[2].click();
actionButton.click(); actionButton.click();
assertTrue(languageHelper.isInputMethodEnabled( assertTrue(languageHelper.isInputMethodEnabled(
'_comp_ime_abcdefghijklmnopqrstuvwxyzabcdefxkb:sw:sw')); '_comp_ime_abcdefghijklmnopqrstuvwxyzabcdefxkb:sw:sw'));
assertTrue(languageHelper.isInputMethodEnabled( assertFalse(languageHelper.isInputMethodEnabled(
'_comp_ime_abcdefghijklmnopqrstuvwxyzabcdefxkb:us:sw')); '_comp_ime_abcdefghijklmnopqrstuvwxyzabcdefxkb:us:sw'));
assertTrue(languageHelper.isInputMethodEnabled(
'_comp_ime_abcdefghijklmnopqrstuvwxyzabcdefxkb:vi:vi'));
}); });
test('suggested input methods hidden when no languages is enabled', () => { test('suggested input methods hidden when no languages is enabled', () => {
...@@ -245,6 +267,47 @@ suite('input page', () => { ...@@ -245,6 +267,47 @@ suite('input page', () => {
assertTrue(!!suggestedInputMethods); assertTrue(!!suggestedInputMethods);
assertEquals('none', getComputedStyle(suggestedInputMethods).display); assertEquals('none', getComputedStyle(suggestedInputMethods).display);
}); });
test('searches input methods correctly', () => {
const searchInput = dialog.$$('cr-search-field');
const getItems = function() {
return allInputMethods.querySelectorAll('.list-item:not([hidden])');
};
assertFalse(dialog.$$('#allInputMethodsLabel').hidden);
assertEquals('block', getComputedStyle(suggestedInputMethods).display);
// Expecting a few languages to be displayed when no query exists.
assertGE(getItems().length, 1);
// Search hides suggestedInputMethods and allInputMethodsLabel.
searchInput.setValue('v');
Polymer.dom.flush();
assertTrue(dialog.$$('#allInputMethodsLabel').hidden);
assertEquals('none', getComputedStyle(suggestedInputMethods).display);
// Search input methods name
searchInput.setValue('vietnamese');
Polymer.dom.flush();
assertEquals(1, getItems().length);
assertTrue(getItems()[0].textContent.includes('Vietnamese'));
});
test('has escape key behavior working correctly', function() {
const searchInput = dialog.$$('cr-search-field');
searchInput.setValue('dummyquery');
// Test that dialog is not closed if 'Escape' is pressed on the input
// and a search query exists.
MockInteractions.keyDownOn(searchInput, 19, [], 'Escape');
assertTrue(dialog.$.dialog.open);
// Test that dialog is closed if 'Escape' is pressed on the input and no
// search query exists.
searchInput.setValue('');
MockInteractions.keyDownOn(searchInput, 19, [], 'Escape');
assertFalse(dialog.$.dialog.open);
});
}); });
suite('records metrics', () => { suite('records metrics', () => {
......
...@@ -163,7 +163,13 @@ cr.define('settings', function() { ...@@ -163,7 +163,13 @@ cr.define('settings', function() {
displayName: 'US Swahili keyboard', displayName: 'US Swahili keyboard',
languageCodes: ['en', 'en-US', 'sw'], languageCodes: ['en', 'en-US', 'sw'],
enabled: false, enabled: false,
} },
{
id: '_comp_ime_abcdefghijklmnopqrstuvwxyzabcdefxkb:vi:vi',
displayName: 'Vietnamese keyboard',
languageCodes: ['vi'],
enabled: false,
},
]; ];
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment