Commit 020e1eb7 authored by Monica Basta's avatar Monica Basta Committed by Commit Bot

[Settings]: Implement manage profile new design.

This CL implements the new UI design for
'chrome://settings/manageProfile'. The previous implementation had a
input placeholder to allow edit the local profile name and profile
avatar selector. The new UI has 4 sections:
- Name your profile
- Pick a theme color
- Pick an avatar
- Create desktop shortcut (shown only on Windows devices)

All changes are visual changes, no behavioral changes are expected
except for the ability to change the profile theme color.

Bug: 1147088
Change-Id: Ib4cdea31a97c291b55ccaa2e6e3ec30f54786d14
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2527125
Commit-Queue: Monica Basta <msalama@chromium.org>
Reviewed-by: default avatardpapad <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826829}
parent 3258b123
......@@ -2839,6 +2839,24 @@
<message name="IDS_SETTINGS_PROFILE_SHORTCUT_TOGGLE_LABEL" desc="Label of the toggle that creates or removes a desktop shortcut for the profile.">
Show desktop shortcut
</message>
<message name="IDS_SETTINGS_CUSTOMIZE_PROFILE" desc="Title of the manage profile subpage. This is a subpage to customize Chrome Profile.">
Customize profile
</message>
<message name="IDS_SETTING_NAME_YOUR_PROFILE" desc="Title of the edit local profile name section on the manage profile page.">
Name your profile
</message>
<message name="IDS_SETTINGS_PICK_A_THEME_COLOR" desc="Title of edit profile theme color section on the manage profile page.">
Pick a theme color
</message>
<message name="IDS_SETTINGS_PICK_AN_AVATAR" desc="Title of the edit avatar selection section on the manage profile page.">
Pick an avatar
</message>
<message name="IDS_SETTINGS_CREATE_SHORTCUT" desc="Title of create shortcut section on the manage profile page.">
Create desktop shortcut
</message>
<message name="IDS_SETTINGS_CREATE_SHORTCUT_SUBTITLE" desc="Subtitle of create shortcut section on the manage profile page.">
Create a desktop shortcut on your device to access directly to this profile
</message>
</if>
<message name="IDS_SETTINGS_SYNC_DISCONNECT_EXPLANATION" desc="The text to display in the Sign out of Chrome dialog to stop syncing for non-managed profiles.">
......
c8d0127ddfcfbecd75ebedfbb4d66ef49c471dd7
\ No newline at end of file
c8d0127ddfcfbecd75ebedfbb4d66ef49c471dd7
\ No newline at end of file
20909e83c0a3f92e33a626b165e9658211340f13
\ No newline at end of file
6955755686670617217a8e8c2fc479495f06e88b
\ No newline at end of file
6b9b5d19346007ff09fb5082c72f70b81c947907
\ No newline at end of file
276172e7153aab268e941224bcb855ba30fc99ac
\ No newline at end of file
......@@ -6,19 +6,117 @@
#avatarSelector {
margin: 16px 48px;
}
.cr-row.manage-profile-section {
display: block;
padding-bottom: 24px;
}
.cr-row:not(.first) {
padding-top: 4px;
}
.content {
--icon-grid-gap: 25px;
--icon-size: 72px;
padding-inline-start: var(--cr-section-indent-width);
padding-top: 4px;
}
#name {
width: 288px;
}
#themeSelector {
--selected-border: 9px; /* Selected border per side: 4.5px*/
--cr-customize-themes-grid-gap: calc(var(--icon-grid-gap) -
var(--selected-border));
--cr-customize-themes-icon-size: calc(var(--icon-size) +
var(--selected-border));
}
#profileAvatarSelector {
--avatar-size: var(--icon-size);
--avatar-spacing: var(--icon-grid-gap);
--avatar-grid-columns: 6;
padding: 4px;
width: calc(var(--avatar-size) * var(--avatar-grid-columns) +
var(--avatar-spacing) * (var(--avatar-grid-columns) - 1));
}
#outerRow {
align-items: center;
display: flex;
min-height: var(--settings-row-two-line-min-height);
padding: 0 var(--cr-section-padding);
}
#labelWrapper {
padding: var(--cr-section-vertical-padding) 0;
}
</style>
<div class="cr-row first">
<cr-input id="name" value="[[profileName]]" pattern="[[pattern_]]"
on-change="onProfileNameChanged_" on-keydown="onProfileNameKeydown_"
disabled="[[isProfileNameDisabled_(syncStatus)]]" maxlength="500"
aria-label="$i18n{nameInputLabel}" auto-validate required
spellcheck="false">
</cr-input>
<template is="dom-if" if="[[!isProfilesUIRevamp_]]">
<div class="cr-row first">
<cr-input id="name" value="[[profileName]]" pattern="[[pattern_]]"
on-change="onProfileNameChanged_" on-keydown="onProfileNameKeydown_"
disabled="[[isProfileNameDisabled_(syncStatus)]]" maxlength="500"
aria-label="$i18n{nameInputLabel}" auto-validate required
spellcheck="false">
</cr-input>
</div>
<template is="dom-if" if="[[isProfileShortcutSettingVisible_]]">
<div class="cr-row first">
<div id="showShortcutLabel" class="flex cr-padded-text">
$i18n{showShortcutLabel}
</div>
<cr-toggle id="hasShortcutToggle"
checked="{{hasProfileShortcut_}}"
on-change="onHasProfileShortcutChange_"
aria-labelledby="showShortcutLabel">
</cr-toggle>
</div>
</template>
<cr-profile-avatar-selector
id="avatarSelector" avatars="[[availableIcons]]"
selected-avatar="{{profileAvatar_}}" ignore-modified-key-events>
</cr-profile-avatar-selector>
</template>
<template is="dom-if" if="[[isProfilesUIRevamp_]]">
<div class="cr-row first manage-profile-section">
<h1 class="cr-title-text">$i18n{nameYourProfile}</h1>
<div class="content">
<cr-input id="name" value="[[profileName]]" pattern="[[pattern_]]"
on-change="onProfileNameChanged_"
on-keydown="onProfileNameKeydown_"
disabled="[[isProfileNameDisabled_(syncStatus)]]" maxlength="500"
aria-label="$i18n{nameInputLabel}" auto-validate required
spellcheck="false">
</cr-input>
</div>
</div>
<div class="cr-row manage-profile-section">
<h1 class="cr-title-text">$i18n{pickThemeColor}</h1>
<div class="content">
<cr-customize-themes id="themeSelector" auto-confirm-theme-changes>
</cr-customize-themes>
</div>
</div>
<div class="cr-row manage-profile-section">
<h1 class="cr-title-text">$i18n{pickAvatar}</h1>
<div class="content">
<cr-profile-avatar-selector
id="profileAvatarSelector" avatars="[[availableIcons]]"
selected-avatar="{{profileAvatar_}}" ignore-modified-key-events>
</cr-profile-avatar-selector>
</div>
</div>
<template is="dom-if" if="[[isProfileShortcutSettingVisible_]]">
<div class="cr-row first">
<div id="showShortcutLabel" class="flex cr-padded-text">
$i18n{showShortcutLabel}
<div id="outerRow" class="hr">
<div class="flex" id="labelWrapper">
<div>$i18n{createShortcutTitle}</div>
<div class="secondary">$i18n{createShortcutSubtitle}</div>
</div>
<cr-toggle id="hasShortcutToggle"
checked="{{hasProfileShortcut_}}"
......@@ -26,11 +124,6 @@
aria-labelledby="showShortcutLabel">
</cr-toggle>
</div>
<div class="hr"></div>
</template>
<template is="dom-if" if="[[isCustomizeThemesVisible_]]">
<cr-customize-themes id="themeSelector" auto-confirm-theme-changes>
</cr-customize-themes>
</template>
<cr-profile-avatar-selector id="avatarSelector" avatars="[[availableIcons]]"
selected-avatar="{{profileAvatar_}}" ignore-modified-key-events>
</cr-profile-avatar-selector>
</template>
......@@ -78,12 +78,12 @@ Polymer({
isProfileShortcutSettingVisible_: Boolean,
/**
* True if the customize themes feature is enabled.
* True if the 'kProfilesUIRevamp' feature is enabled.
* @private
*/
isCustomizeThemesVisible_: {
isProfilesUIRevamp_: {
type: Boolean,
value: () => loadTimeData.getBoolean('profileThemeSelectorEnabled')
value: () => loadTimeData.getBoolean('profilesUIRevamp')
},
/**
......@@ -121,7 +121,11 @@ Polymer({
currentRouteChanged() {
if (Router.getInstance().getCurrentRoute() === routes.MANAGE_PROFILE) {
if (this.profileName) {
this.$.name.value = this.profileName;
const profileNameInput =
/** @type {CrInputElement} */ (this.$$('#name'));
if (profileNameInput) {
profileNameInput.value = this.profileName;
}
}
if (loadTimeData.getBoolean('profileShortcutsEnabled')) {
this.browserProxy_.getProfileShortcutStatus().then(status => {
......
......@@ -28,6 +28,7 @@
#include "chrome/browser/signin/account_consistency_mode_manager.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/webui/management_ui.h"
#include "chrome/browser/ui/webui/policy_indicator_localized_strings_provider.h"
#include "chrome/browser/ui/webui/settings/reset_settings_handler.h"
......@@ -1196,7 +1197,6 @@ void AddPeopleStrings(content::WebUIDataSource* html_source, Profile* profile) {
#if defined(OS_CHROMEOS)
{"accountManagerSubMenuLabel", IDS_SETTINGS_ACCOUNT_MANAGER_SUBMENU_LABEL},
#else
{"editPerson", IDS_SETTINGS_EDIT_PERSON},
{"profileNameAndPicture", IDS_SETTINGS_PROFILE_NAME_AND_PICTURE},
#endif
......@@ -1204,6 +1204,17 @@ void AddPeopleStrings(content::WebUIDataSource* html_source, Profile* profile) {
#if !defined(OS_CHROMEOS)
{"showShortcutLabel", IDS_SETTINGS_PROFILE_SHORTCUT_TOGGLE_LABEL},
{"nameInputLabel", IDS_SETTINGS_PROFILE_NAME_INPUT_LABEL},
{"nameYourProfile", IDS_SETTING_NAME_YOUR_PROFILE},
{"pickThemeColor", IDS_SETTINGS_PICK_A_THEME_COLOR},
{"pickAvatar", IDS_SETTINGS_PICK_AN_AVATAR},
{"createShortcutTitle", IDS_SETTINGS_CREATE_SHORTCUT},
{"createShortcutSubtitle", IDS_SETTINGS_CREATE_SHORTCUT_SUBTITLE},
// Color picker strings:
{"colorPickerLabel", IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL},
{"defaultThemeLabel", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
{"thirdPartyThemeDescription", IDS_NTP_CUSTOMIZE_3PT_THEME_DESC},
{"uninstallThirdPartyThemeButton", IDS_NTP_CUSTOMIZE_3PT_THEME_UNINSTALL},
#endif
{"deleteProfileWarningExpandA11yLabel",
IDS_SETTINGS_SYNC_DISCONNECT_EXPAND_ACCESSIBILITY_LABEL},
......@@ -1213,12 +1224,6 @@ void AddPeopleStrings(content::WebUIDataSource* html_source, Profile* profile) {
IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITH_COUNTS_PLURAL},
{"deleteProfileWarningWithoutCounts",
IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITHOUT_COUNTS},
// Color picker strings:
{"colorPickerLabel", IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL},
{"defaultThemeLabel", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
{"thirdPartyThemeDescription", IDS_NTP_CUSTOMIZE_3PT_THEME_DESC},
{"uninstallThirdPartyThemeButton", IDS_NTP_CUSTOMIZE_3PT_THEME_UNINSTALL},
};
AddLocalizedStringsBulk(html_source, kLocalizedStrings);
......@@ -1231,6 +1236,13 @@ void AddPeopleStrings(content::WebUIDataSource* html_source, Profile* profile) {
.spec());
html_source->AddBoolean("profileShortcutsEnabled",
ProfileShortcutManager::IsFeatureEnabled());
#if !defined(OS_CHROMEOS)
html_source->AddLocalizedString(
"editPerson", base::FeatureList::IsEnabled(features::kProfilesUIRevamp)
? IDS_SETTINGS_CUSTOMIZE_PROFILE
: IDS_SETTINGS_EDIT_PERSON);
#endif
#if defined(OS_CHROMEOS)
// Toggles the Chrome OS Account Manager submenu in the People section.
html_source->AddBoolean("isAccountManagerEnabled",
......
......@@ -303,9 +303,8 @@ SettingsUI::SettingsUI(content::WebUI* web_ui)
// This is the browser settings page.
html_source->AddBoolean("isOSSettings", false);
#else // defined(OS_CHROMEOS)
html_source->AddBoolean(
"profileThemeSelectorEnabled",
base::FeatureList::IsEnabled(features::kProfilesUIRevamp));
html_source->AddBoolean("profilesUIRevamp", base::FeatureList::IsEnabled(
features::kProfilesUIRevamp));
#endif // !defined(OS_CHROMEOS)
AddSettingsPageUIHandler(std::make_unique<AboutHandler>(profile));
......
......@@ -80,8 +80,7 @@ suite('ManageProfileTests', function() {
setup(function() {
browserProxy = new TestManageProfileBrowserProxy();
ManageProfileBrowserProxyImpl.instance_ = browserProxy;
PolymerTest.clearBody();
manageProfile = createManageProfileElement();
setFlags({profilesUIRevamp: false, profileShortcutsEnabled: false});
Router.getInstance().navigateTo(routes.MANAGE_PROFILE);
});
......@@ -99,73 +98,102 @@ suite('ManageProfileTests', function() {
return manageProfileElement;
}
/**
* Recreates manage profile element with overridden loadTimeData.
* @param {Object} replacements The dictionary object of keys to replace.
*/
function setFlags(replacements) {
PolymerTest.clearBody();
loadTimeData.overrideValues(replacements);
manageProfile = createManageProfileElement();
flush();
}
// Tests that the manage profile subpage
// - gets and receives all the available icons
// - can select a new icon
test('ManageProfileChangeIcon', function() {
test('ManageProfileChangeIcon', async function() {
let items = null;
return browserProxy.whenCalled('getAvailableIcons')
.then(function() {
flush();
items =
manageProfile.$.avatarSelector.$['avatar-grid'].querySelectorAll(
'.avatar');
assertFalse(!!manageProfile.profileAvatar);
assertEquals(3, items.length);
assertFalse(items[0].classList.contains('iron-selected'));
assertTrue(items[1].classList.contains('iron-selected'));
assertFalse(items[2].classList.contains('iron-selected'));
items[1].click();
return browserProxy.whenCalled('setProfileIconToDefaultAvatar');
})
.then(function(args) {
assertEquals('fake-icon-2.png', args[0]);
items[2].click();
return browserProxy.whenCalled('setProfileIconToGaiaAvatar');
});
async function changeIcon() {
await browserProxy.whenCalled('getAvailableIcons');
flush();
items = manageProfile.$$('cr-profile-avatar-selector')
.$$('#avatar-grid')
.querySelectorAll('.avatar');
assertFalse(!!manageProfile.profileAvatar);
assertEquals(3, items.length);
assertFalse(items[0].classList.contains('iron-selected'));
assertTrue(items[1].classList.contains('iron-selected'));
assertFalse(items[2].classList.contains('iron-selected'));
items[1].click();
const args =
await browserProxy.whenCalled('setProfileIconToDefaultAvatar');
assertEquals('fake-icon-2.png', args[0]);
items[2].click();
await browserProxy.whenCalled('setProfileIconToGaiaAvatar');
}
await changeIcon();
browserProxy.reset();
setFlags({profilesUIRevamp: true});
await changeIcon();
});
test('ManageProfileChangeName', function() {
const nameField = manageProfile.$.name;
assertTrue(!!nameField);
assertFalse(!!nameField.disabled);
assertEquals('.*\\S.*', nameField.pattern);
test('ManageProfileChangeName', async function() {
async function changeName() {
const nameField = manageProfile.$$('#name');
assertTrue(!!nameField);
assertFalse(!!nameField.disabled);
assertEquals('.*\\S.*', nameField.pattern);
assertEquals('Initial Fake Name', nameField.value);
assertEquals('Initial Fake Name', nameField.value);
nameField.value = 'New Name';
nameField.fire('change');
nameField.value = 'New Name';
nameField.fire('change');
return browserProxy.whenCalled('setProfileName').then(function(args) {
const args = await browserProxy.whenCalled('setProfileName');
assertEquals('New Name', args[0]);
});
}
await changeName();
browserProxy.resetResolver('setProfileName');
setFlags({profilesUIRevamp: true});
await changeName();
});
test('ProfileNameIsDisabledForSupervisedUser', function() {
manageProfile.syncStatus = {supervisedUser: true, childUser: false};
const nameField = manageProfile.$.name;
assertTrue(!!nameField);
// Name field should be disabled for legacy supervised users.
assertTrue(!!nameField.disabled);
function profileNameDisabledForSupervisedUser() {
manageProfile.syncStatus = {supervisedUser: true, childUser: false};
const nameField = manageProfile.$$('#name');
assertTrue(!!nameField);
// Name field should be disabled for legacy supervised users.
assertTrue(!!nameField.disabled);
}
profileNameDisabledForSupervisedUser();
setFlags({profilesUIRevamp: true});
profileNameDisabledForSupervisedUser();
});
// Tests profile name updates pushed from the browser.
test('ManageProfileNameUpdated', function() {
const nameField = manageProfile.$.name;
assertTrue(!!nameField);
test('ManageProfileNameUpdated', async function() {
async function profileNameUpdated() {
const nameField = manageProfile.$$('#name');
assertTrue(!!nameField);
return browserProxy.whenCalled('getAvailableIcons').then(function() {
await browserProxy.whenCalled('getAvailableIcons');
manageProfile.profileName = 'New Name From Browser';
flush();
assertEquals('New Name From Browser', nameField.value);
});
}
await profileNameUpdated();
browserProxy.resetResolver('getAvailableIcons');
setFlags({profilesUIRevamp: true});
await profileNameUpdated();
});
// Tests profile shortcut toggle is hidden if profile shortcuts feature is
......@@ -176,59 +204,24 @@ suite('ManageProfileTests', function() {
});
// Tests that the theme selector is hidden if profile colors feature is
// disabled.
test('ProfileThemeSelectorHidden', function() {
// Tests that the theme selector is visible if new manage profile design
// feature is enabled and hidden otherwise.
test('ProfileThemeSelector', function() {
assertFalse(!!manageProfile.$$('#themeSelector'));
});
// Tests that the theme selector is visible if profile colors feature is
// enabled.
test('ProfileThemeSelectorVisible', function() {
// Recreate a manage profile element with overridden loadTimeData.
PolymerTest.clearBody();
loadTimeData.overrideValues({
profileThemeSelectorEnabled: true,
});
manageProfile = createManageProfileElement();
flush();
setFlags({profilesUIRevamp: true});
assertTrue(!!manageProfile.$$('#themeSelector'));
});
});
suite('ManageProfileTestsProfileShortcutsEnabled', function() {
let manageProfile = null;
let browserProxy = null;
setup(function() {
loadTimeData.overrideValues({
profileShortcutsEnabled: true,
});
browserProxy = new TestManageProfileBrowserProxy();
ManageProfileBrowserProxyImpl.instance_ = browserProxy;
PolymerTest.clearBody();
manageProfile = document.createElement('settings-manage-profile');
manageProfile.profileIconUrl = 'fake-icon-1.png';
manageProfile.profileName = 'Initial Fake Name';
manageProfile.syncStatus = {supervisedUser: false, childUser: false};
document.body.appendChild(manageProfile);
});
teardown(function() {
manageProfile.remove();
});
// Tests profile shortcut toggle is visible and toggling it removes and
// creates the profile shortcut respectively.
test('ManageProfileShortcutToggle', function() {
Router.getInstance().navigateTo(routes.MANAGE_PROFILE);
flush();
assertFalse(!!manageProfile.$$('#hasShortcutToggle'));
test('ManageProfileShortcutToggle', async function() {
async function profileShortcutToggle() {
assertFalse(!!manageProfile.$$('#hasShortcutToggle'));
await browserProxy.whenCalled('getProfileShortcutStatus');
return browserProxy.whenCalled('getProfileShortcutStatus').then(function() {
flush();
const hasShortcutToggle = manageProfile.$$('#hasShortcutToggle');
......@@ -239,55 +232,67 @@ suite('ManageProfileTestsProfileShortcutsEnabled', function() {
// Simulate tapping the profile shortcut toggle.
hasShortcutToggle.click();
return browserProxy.whenCalled('removeProfileShortcut').then(function() {
flush();
await browserProxy.whenCalled('removeProfileShortcut');
// The profile shortcut toggle is checked.
assertFalse(hasShortcutToggle.checked);
flush();
// Simulate tapping the profile shortcut toggle.
hasShortcutToggle.click();
return browserProxy.whenCalled('addProfileShortcut');
});
});
// The profile shortcut toggle is checked.
assertFalse(hasShortcutToggle.checked);
// Simulate tapping the profile shortcut toggle.
hasShortcutToggle.click();
return browserProxy.whenCalled('addProfileShortcut');
}
setFlags({profilesUIRevamp: false, profileShortcutsEnabled: true});
await profileShortcutToggle();
browserProxy.reset();
setFlags({profilesUIRevamp: true, profileShortcutsEnabled: true});
await profileShortcutToggle();
});
// Tests profile shortcut toggle is visible and toggled off when no
// profile shortcut is found.
test('ManageProfileShortcutToggle', function() {
test('ManageProfileShortcutToggleShortcutNotFound', async function() {
browserProxy.setProfileShortcutStatus(
ProfileShortcutStatus.PROFILE_SHORTCUT_NOT_FOUND);
Router.getInstance().navigateTo(routes.MANAGE_PROFILE);
flush();
assertFalse(!!manageProfile.$$('#hasShortcutToggle'));
async function profileShortcutToggleShortcutNotFound() {
assertFalse(!!manageProfile.$$('#hasShortcutToggle'));
await browserProxy.whenCalled('getProfileShortcutStatus');
return browserProxy.whenCalled('getProfileShortcutStatus').then(function() {
flush();
const hasShortcutToggle = manageProfile.$$('#hasShortcutToggle');
assertTrue(!!hasShortcutToggle);
assertFalse(hasShortcutToggle.checked);
});
}
setFlags({profilesUIRevamp: false, profileShortcutsEnabled: true});
await profileShortcutToggleShortcutNotFound();
browserProxy.resetResolver('getProfileShortcutStatus');
setFlags({profilesUIRevamp: true, profileShortcutsEnabled: true});
await profileShortcutToggleShortcutNotFound();
});
// Tests the case when the profile shortcut setting is hidden. This can
// occur in the single profile case.
test('ManageProfileShortcutSettingHIdden', function() {
test('ManageProfileShortcutSettingHidden', async function() {
browserProxy.setProfileShortcutStatus(
ProfileShortcutStatus.PROFILE_SHORTCUT_SETTING_HIDDEN);
Router.getInstance().navigateTo(routes.MANAGE_PROFILE);
flush();
async function profileShortcutSettingHidden() {
assertFalse(!!manageProfile.$$('#hasShortcutToggle'));
assertFalse(!!manageProfile.$$('#hasShortcutToggle'));
await browserProxy.whenCalled('getProfileShortcutStatus');
return browserProxy.whenCalled('getProfileShortcutStatus').then(function() {
flush();
assertFalse(!!manageProfile.$$('#hasShortcutToggle'));
});
}
setFlags({profilesUIRevamp: false, profileShortcutsEnabled: true});
await profileShortcutSettingHidden();
browserProxy.resetResolver('getProfileShortcutStatus');
setFlags({profilesUIRevamp: true, profileShortcutsEnabled: true});
await profileShortcutSettingHidden();
});
});
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