Commit e54e3567 authored by Monica Basta's avatar Monica Basta Committed by Commit Bot

[ProfilePicker]: Add create local profile functionality.

This CL adds 'Done' button that on click will create a new profile. Once
the profile is created, it is opened and the picker is closed. This CL
also adjusts the layout of the page based on the latest mocks that takes
into consideration different screen sizes.

Screenshots:
https://drive.google.com/file/d/1js21n3dxRaSkOqdBnn3v27e096UaiPE5/view?usp=sharing
https://drive.google.com/file/d/11SIROfvDnIud8JkqZDsmlegnNkoDwmYD/view?usp=sharing
https://drive.google.com/file/d/1x12IWRlhcQbLf4cmJ_1WoeQXzc9WDmn3/view?usp=sharing
https://drive.google.com/file/d/17cfK6xuAkim9jvNxIgHIUVVljwOsUTu0/view?usp=sharing
https://drive.google.com/file/d/1bkzZlNmae1ZIA_7b_13vf-THGcNZ8nD0/view?usp=sharing
https://drive.google.com/file/d/1GnnSQ4qTRJxXEnsOPLyWoaNjnO_4zxDa/view?usp=sharing

Bug: 1115056
Change-Id: I72c6f2ee07751b61f264443eab468583f7b2c95a
Fixed: 1121662
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2375292
Auto-Submit: Monica Basta <msalama@chromium.org>
Commit-Queue: Monica Basta <msalama@chromium.org>
Commit-Queue: David Roger <droger@chromium.org>
Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Reviewed-by: default avatarEsmael Elmoslimany <aee@chromium.org>
Reviewed-by: default avatarAlex Ilin <alexilin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#801790}
parent e831936f
......@@ -42,8 +42,8 @@ class ProfileMetrics {
ADD_NEW_USER_MENU = 1,
// User adds new user from create-profile dialog
ADD_NEW_USER_DIALOG = 2,
// User adds new user from User Manager -- no longer used
// ADD_NEW_USER_MANAGER = 3,
// User adds new user from Profile Picker
ADD_NEW_PROFILE_PICKER = 3,
// Auto-created after deleting last user
ADD_NEW_USER_LAST_DELETED = 4,
// Created by the sign-in interception prompt
......
......@@ -22,6 +22,7 @@ export let ProfileState;
* This is the data structure sent back and forth between C++ and JS.
* @typedef {{
* colorId: number,
* color: number,
* themeFrameColor: string,
* themeShapeColor: string,
* themeFrameTextColor: string,
......@@ -81,6 +82,17 @@ export class ManageProfilesBrowserProxy {
/** Loads Google sign in page.*/
loadSignInProfileCreationFlow() {}
/**
* Creates local profile
* @param {string} profileName
* @param {number} profileColor
* @param {string} avatarUrl
* @param {boolean} isGeneric
* @param {boolean} createShortcut
*/
createProfile(
profileName, profileColor, avatarUrl, isGeneric, createShortcut) {}
}
/** @implements {ManageProfilesBrowserProxy} */
......@@ -129,6 +141,14 @@ export class ManageProfilesBrowserProxyImpl {
loadSignInProfileCreationFlow() {
chrome.send('loadSignInProfileCreationFlow');
}
/** @override */
createProfile(
profileName, profileColor, avatarUrl, isGeneric, createShortcut) {
chrome.send(
'createProfile',
[profileName, profileColor, avatarUrl, isGeneric, createShortcut]);
}
}
addSingletonGetter(ManageProfilesBrowserProxyImpl);
......@@ -30,11 +30,14 @@ js_library("local_profile_customization") {
deps = [
"..:manage_profiles_browser_proxy",
"..:navigation_behavior",
"..:policy_helper",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/cr_elements/cr_button:cr_button.m",
"//ui/webui/resources/cr_elements/cr_checkbox:cr_checkbox.m",
"//ui/webui/resources/cr_elements/cr_icon_button:cr_icon_button.m",
"//ui/webui/resources/cr_elements/cr_input:cr_input.m",
"//ui/webui/resources/js:load_time_data.m",
"//ui/webui/resources/js:web_ui_listener_behavior.m",
]
}
......
......@@ -10,17 +10,58 @@
text-align: center;
}
#wrapperContainer {
align-items: center;
display: flex;
height: calc(max(100vh, var(--view-min-size)) -
(var(--banner-height) + var(--avatar-size)/2 + var(--vertical-gap) +
var(--cr-button-height) + var(--footer-margin)));
justify-content: center;
margin-bottom: var(--vertical-gap);
margin-inline-end: 16px;
margin-inline-start: 16px;
margin-top: calc(var(--avatar-size)/2);
overflow: auto;
}
#wrapperContainer::-webkit-scrollbar {
width: var(--scrollbar-width);
}
/* Track */
#wrapperContainer::-webkit-scrollbar-track {
border-radius: var(--scrollbar-width);
}
/* Handle */
#wrapperContainer::-webkit-scrollbar-thumb {
background: var(--scrollbar-background);
border-radius: var(--scrollbar-width);
}
#wrapper > * {
flex-grow: 0;
flex-shrink: 0;
margin-top: var(--vertical-gap);
}
#wrapper {
align-items: center;
display: flex;
flex-direction: column;
max-height: 100%;
width: 100%;
}
#nameInput {
--cr-input-placeholder-color: rgba(var(--google-grey-900-rgb), .5);
--cr-input-border-bottom: 1px solid var(--cr-secondary-text-color);
height: 32px;
margin: calc(var(--avatar-size)/2 + var(--vertical-gap)) auto 0 auto;
width: 300px;
}
#colorPicker {
height: 220px;
margin: var(--vertical-gap) auto var(--vertical-gap) auto;
width: 370px;
}
......@@ -28,7 +69,11 @@
--cr-checkbox-label-color: var(--cr-secondary-text-color);
--cr-checkbox-label-padding-start: 8px;
height: 20px;
margin: auto;
left: 0;
margin-inline-end: auto;
margin-inline-start: auto;
position: absolute;
right: 0;
width: fit-content;
}
......@@ -36,6 +81,13 @@
display: none;
}
#save {
display: flex;
margin-inline-end: var(--footer-margin);
margin-inline-start: auto;
width: 111px;
}
@media (prefers-color-scheme: dark) {
#nameInput {
--cr-input-placeholder-color: rgba(var(--google-grey-200-rgb), .5);
......@@ -55,17 +107,28 @@
<img class="avatar" src$="[[profileThemeInfo.themeGenericAvatar]]">
</div>
<cr-input id="nameInput" value="{{profileName_}}" pattern=".*\\S.*"
placeholder="$i18n{createProfileNamePlaceholder}"
auto-validate spellcheck="false">
</cr-input>
<div id="wrapperContainer">
<div id="wrapper">
<cr-input id="nameInput" value="{{profileName_}}" pattern=".*\\S.*"
placeholder="$i18n{createProfileNamePlaceholder}"
auto-validate spellcheck="false">
</cr-input>
<!-- TODO(crbug.com/1115301): Add color picker. -->
<div id="colorPicker"
style$="background-color:[[profileThemeInfo.themeFrameColor]]">
<!-- TODO(crbug.com/1115301): Add color picker. -->
<div id="colorPicker"
style$="background-color:[[profileThemeInfo.themeFrameColor]]">
</div>
</div>
</div>
<cr-checkbox checked="{{createShortcut_}}"
hidden="[[!isProfileShortcutsEnabled_]]">
$i18n{createDesktopShortcutLabel}
</cr-checkbox>
<div class="footer">
<cr-checkbox checked="{{createShortcut_}}"
hidden="[[!isProfileShortcutsEnabled_]]">
$i18n{createDesktopShortcutLabel}
</cr-checkbox>
<cr-button id="save" class="action-button" on-click="onSaveClick_"
disabled="[[isSaveDisabled_(createInProgress_, profileName_)]]">
$i18n{createProfileConfirm}
</cr-button>
</div>
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
......@@ -10,16 +11,20 @@ import './shared_css.js';
import '../icons.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {AutogeneratedThemeColorInfo} from '../manage_profiles_browser_proxy.js';
import {AutogeneratedThemeColorInfo, ManageProfilesBrowserProxy, ManageProfilesBrowserProxyImpl} from '../manage_profiles_browser_proxy.js';
import {navigateToPreviousRoute} from '../navigation_behavior.js';
import {isProfileCreationAllowed} from '../policy_helper.js';
Polymer({
is: 'local-profile-customization',
_template: html`{__html_template__}`,
behaviors: [WebUIListenerBehavior],
properties: {
/** @type {!AutogeneratedThemeColorInfo} */
profileThemeInfo: {
......@@ -52,6 +57,71 @@ Polymer({
type: Boolean,
value: () => loadTimeData.getBoolean('profileShortcutsEnabled'),
},
/**
* True if a profile is being created or imported.
* @private {boolean}
*/
createInProgress_: {
type: Boolean,
value: false,
},
},
/** @private {?ManageProfilesBrowserProxy} */
manageProfilesBrowserProxy_: null,
/** @override */
ready() {
this.sanityCheck_();
this.manageProfilesBrowserProxy_ =
ManageProfilesBrowserProxyImpl.getInstance();
this.addWebUIListener(
'create-profile-finished', () => this.handleCreateProfileFinished_());
},
/**
* @return {boolean}
* @private
*/
sanityCheck_() {
if (!isProfileCreationAllowed()) {
this.onClickBack_();
return false;
}
return true;
},
/**
* Determining whether 'Save' button is disabled.
* @return {boolean}
* @private
*/
isSaveDisabled_() {
return this.createInProgress_ || !this.profileName_ ||
!this.$.nameInput.validate();
},
/**
* Handler for the 'Save' button click event.
* @param {!Event} event
* @private
*/
onSaveClick_(event) {
if (!this.sanityCheck_()) {
return;
}
if (this.createInProgress_) {
return;
}
this.createInProgress_ = true;
const createShortcut =
this.isProfileShortcutsEnabled_ && this.createShortcut_;
// TODO(crbug.com/1115056): Support avatar selection.
this.manageProfilesBrowserProxy_.createProfile(
this.profileName_, this.profileThemeInfo.color, '', true,
createShortcut);
},
/** @private */
......@@ -64,5 +134,12 @@ Polymer({
reset_() {
this.profileName_ = '';
this.createShortcut_ = true;
this.$.wrapper.scrollTop = 0;
},
/** @private */
handleCreateProfileFinished_() {
this.onClickBack_();
this.createInProgress_ = false;
}
});
......@@ -2,11 +2,12 @@
<style include="cr-icons profile-picker-shared">
:host {
--avatar-size: 100px;
--banner-height: 244px;
}
#headerContainer {
background-color: var(--theme-frame-color);
height: 244px;
height: var(--banner-height);
position: relative;
width: 100%;
}
......
......@@ -42,7 +42,6 @@
--grid-gutter: 24px;
--profile-item-height:178px;
--profile-item-width: 162px;
--scrollbar-width: 4px;
align-items: center;
display: grid;
grid-column-gap: var(--grid-gutter);
......@@ -69,7 +68,7 @@
/* Handle */
.profiles-container::-webkit-scrollbar-thumb {
background: var(--google-grey-refresh-100);
background: var(--scrollbar-background);
border-radius: var(--scrollbar-width);
}
......@@ -97,21 +96,12 @@
--cr-icon-button-stroke-color: var(--google-grey-refresh-700);
}
.footer {
bottom: 0;
display: flex;
font-size: var(--text-font-size);
margin-bottom: 40px;
position: absolute;
width: 100%;
}
.footer > * {
background-color: rgba(255, 255, 255, .8);
}
#browseAsGuestButton {
margin-inline-start: 40px;
margin-inline-start: var(--footer-margin);
}
#browseAsGuestButton > iron-icon {
......@@ -122,7 +112,7 @@
--cr-checkbox-label-color: var(--cr-secondary-text-color);
--cr-checkbox-label-padding-start: 8px;
justify-content: flex-end;
margin-inline-end: 40px;
margin-inline-end: var(--footer-margin);
margin-inline-start: auto;
padding-inline-end: 5px;
padding-inline-start: 5px;
......@@ -137,10 +127,6 @@
background: url(images/dark_mode_right_banner_image.svg);
}
.profiles-container::-webkit-scrollbar-thumb {
background: var(--google-grey-800);
}
#addProfile {
border-color: var(--google-grey-refresh-700);
}
......
......@@ -2,6 +2,15 @@
<style>
:host {
--text-font-size: 1.16em;
--scrollbar-width: 4px;
--scrollbar-background: var(--google-grey-refresh-100);
--footer-margin: 40px;
}
@media (prefers-color-scheme: dark) {
:host {
--scrollbar-background: var(--google-grey-800);
}
}
html {
......@@ -32,5 +41,14 @@
white-space: nowrap;
width: 130px;
}
.footer {
bottom: 0;
display: flex;
font-size: var(--text-font-size);
margin-bottom: var(--footer-margin);
position: absolute;
width: 100%;
}
</style>
</template>
......@@ -16,6 +16,9 @@
#include "chrome/browser/profiles/profile_statistics_factory.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/profile_picker.h"
......@@ -84,6 +87,10 @@ void ProfilePickerHandler::RegisterMessages() {
base::BindRepeating(
&ProfilePickerHandler::HandleLoadSignInProfileCreationFlow,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"createProfile",
base::BindRepeating(&ProfilePickerHandler::HandleCreateProfile,
base::Unretained(this)));
}
void ProfilePickerHandler::OnJavascriptAllowed() {
......@@ -164,6 +171,7 @@ void ProfilePickerHandler::HandleGetNewProfileSuggestedThemeInfo(
auto theme_colors = GetAutogeneratedThemeColors(color_info.color);
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetIntKey("colorId", color_info.id);
dict.SetIntKey("color", color_info.color);
dict.SetStringKey("themeFrameColor",
color_utils::SkColorToRgbaString(theme_colors.frame_color));
dict.SetStringKey("themeShapeColor", color_utils::SkColorToRgbaString(
......@@ -180,6 +188,104 @@ void ProfilePickerHandler::HandleGetNewProfileSuggestedThemeInfo(
ResolveJavascriptCallback(callback_id, std::move(dict));
}
void ProfilePickerHandler::HandleCreateProfile(const base::ListValue* args) {
// profileName, profileColor, avatarUrl, isGeneric, createShortcut
base::string16 profile_name;
int profile_color;
std::string avatar_url;
bool is_generic;
bool create_shortcut;
CHECK_EQ(5U, args->GetSize());
args->GetString(0, &profile_name);
args->GetInteger(1, &profile_color);
args->GetString(2, &avatar_url);
args->GetBoolean(3, &is_generic);
args->GetBoolean(4, &create_shortcut);
DCHECK(base::IsStringASCII(avatar_url));
base::TrimWhitespace(profile_name, base::TRIM_ALL, &profile_name);
CHECK(!profile_name.empty());
if (is_generic) {
avatar_url = profiles::GetDefaultAvatarIconUrl(
profiles::GetPlaceholderAvatarIndex());
}
#ifndef NDEBUG
size_t icon_index;
DCHECK(profiles::IsDefaultAvatarIconUrl(avatar_url, &icon_index));
#endif
ProfileMetrics::LogProfileAddNewUser(ProfileMetrics::ADD_NEW_PROFILE_PICKER);
ProfileManager::CreateMultiProfileAsync(
profile_name, avatar_url,
base::BindRepeating(&ProfilePickerHandler::OnProfileCreated,
weak_factory_.GetWeakPtr(), SkColor(profile_color),
create_shortcut));
}
void ProfilePickerHandler::OnProfileCreated(SkColor profile_color,
bool create_shortcut,
Profile* profile,
Profile::CreateStatus status) {
switch (status) {
case Profile::CREATE_STATUS_LOCAL_FAIL: {
NOTREACHED() << "Local fail in creating new profile";
break;
}
case Profile::CREATE_STATUS_CREATED:
// Do nothing for an intermediate status.
return;
case Profile::CREATE_STATUS_INITIALIZED: {
OnProfileCreationSuccess(profile_color, create_shortcut, profile);
break;
}
// User-initiated cancellation is handled in CancelProfileRegistration and
// does not call this callback.
case Profile::CREATE_STATUS_CANCELED:
case Profile::CREATE_STATUS_REMOTE_FAIL:
case Profile::MAX_CREATE_STATUS: {
NOTREACHED();
break;
}
}
if (IsJavascriptAllowed())
FireWebUIListener("create-profile-finished", base::Value());
}
void ProfilePickerHandler::OnProfileCreationSuccess(SkColor profile_color,
bool create_shortcut,
Profile* profile) {
DCHECK(profile);
DCHECK(!signin_util::IsForceSigninEnabled());
// Apply a new color to the profile.
ThemeServiceFactory::GetForProfile(profile)->BuildAutogeneratedThemeFromColor(
profile_color);
// Create shortcut if edded.
if (create_shortcut) {
DCHECK(ProfileShortcutManager::IsFeatureEnabled());
ProfileShortcutManager* shortcut_manager =
g_browser_process->profile_manager()->profile_shortcut_manager();
DCHECK(shortcut_manager);
if (shortcut_manager)
shortcut_manager->CreateProfileShortcut(profile->GetPath());
}
// Launch profile and close the picker.
profiles::OpenBrowserWindowForProfile(
base::Bind(&ProfilePickerHandler::OnSwitchToProfileComplete,
weak_factory_.GetWeakPtr(), false),
false, // Don't create a window if one already exists.
true, // Create a first run window.
false, // There is no need to unblock all extensions because we only open
// browser window if the Profile is not locked. Hence there is no
// extension blocked.
profile, Profile::CREATE_STATUS_INITIALIZED);
}
void ProfilePickerHandler::HandleRemoveProfile(const base::ListValue* args) {
CHECK_EQ(1U, args->GetSize());
const base::Value& profile_path_value = args->GetList()[0];
......
......@@ -30,10 +30,13 @@ class ProfilePickerHandler : public content::WebUIMessageHandler,
const base::ListValue* args);
void HandleLaunchGuestProfile(const base::ListValue* args);
void HandleAskOnStartupChanged(const base::ListValue* args);
void HandleGetNewProfileSuggestedThemeInfo(const base::ListValue* args);
void HandleRemoveProfile(const base::ListValue* args);
void HandleGetProfileStatistics(const base::ListValue* args);
// TODO(crbug.com/1115056): Move to new handler for profile creation.
void HandleLoadSignInProfileCreationFlow(const base::ListValue* args);
void HandleGetNewProfileSuggestedThemeInfo(const base::ListValue* args);
void HandleCreateProfile(const base::ListValue* args);
void GatherProfileStatistics(Profile* profile);
void OnProfileStatisticsReceived(base::FilePath profile_path,
......@@ -41,6 +44,13 @@ class ProfilePickerHandler : public content::WebUIMessageHandler,
void OnSwitchToProfileComplete(bool open_settings,
Profile* profile,
Profile::CreateStatus profile_create_status);
void OnProfileCreated(SkColor profile_color,
bool create_shortcut,
Profile* profile,
Profile::CreateStatus status);
void OnProfileCreationSuccess(SkColor profile_color,
bool create_shortcut,
Profile* profile);
void PushProfilesList();
base::Value GetProfilesList();
......
......@@ -78,6 +78,8 @@ void AddStrings(content::WebUIDataSource* html_source) {
IDS_PROFILE_PICKER_PROFILE_CREATION_FLOW_LOCAL_PROFILE_CREATION_INPUT_NAME},
{"createDesktopShortcutLabel",
IDS_PROFILE_PICKER_PROFILE_CREATION_FLOW_LOCAL_PROFILE_CREATION_SHORTCUT_TEXT},
{"createProfileConfirm",
IDS_PROFILE_PICKER_PROFILE_CREATION_FLOW_LOCAL_PROFILE_CREATION_DONE},
};
AddLocalizedStringsBulk(html_source, kLocalizedStrings);
html_source->AddBoolean("askOnStartup",
......
......@@ -19,6 +19,7 @@ export class TestManageProfilesBrowserProxy extends TestBrowserProxy {
'removeProfile',
'getProfileStatistics',
'loadSignInProfileCreationFlow',
'createProfile',
]);
}
......@@ -52,6 +53,7 @@ export class TestManageProfilesBrowserProxy extends TestBrowserProxy {
this.methodCalled('getNewProfileSuggestedThemeInfo');
return Promise.resolve({
colorId: 0,
color: 0,
themeFrameColor: '',
themeShapeColor: '',
themeFrameTextColor: '',
......@@ -73,4 +75,12 @@ export class TestManageProfilesBrowserProxy extends TestBrowserProxy {
loadSignInProfileCreationFlow() {
this.methodCalled('loadSignInProfileCreationFlow');
}
/** @override */
createProfile(
profileName, profileColor, avatarUrl, isGeneric, createShortcut) {
this.methodCalled(
'createProfile',
[profileName, profileColor, avatarUrl, isGeneric, createShortcut]);
}
}
......@@ -58151,7 +58151,7 @@ https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
<int value="0" label="(Deprecated) Add new user from icon menu"/>
<int value="1" label="Add new user from title bar menu"/>
<int value="2" label="Add new user from settings dialog"/>
<int value="3" label="(Deprecated) Add new user from the User Manager"/>
<int value="3" label="Add new user from the Profile picker"/>
<int value="4" label="Auto-created after deleting last user"/>
<int value="5" label="Add new user from sign-in interception"/>
<int value="6" label="Add new user from the sync flow (to avoid data clash)"/>
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