Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / autofill / card_unmask_prompt_views.cc
blob7ee9feaf4bb8c400f9e916e4b7f7f5c0588fafea
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/autofill/card_unmask_prompt_views.h"
7 #include "base/basictypes.h"
8 #include "base/location.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/browser/ui/autofill/autofill_dialog_types.h"
13 #include "chrome/browser/ui/autofill/create_card_unmask_prompt_view.h"
14 #include "chrome/browser/ui/views/autofill/decorated_textfield.h"
15 #include "chrome/browser/ui/views/autofill/tooltip_icon.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "components/autofill/core/browser/ui/card_unmask_prompt_controller.h"
18 #include "components/constrained_window/constrained_window_views.h"
19 #include "components/web_modal/web_contents_modal_dialog_host.h"
20 #include "components/web_modal/web_contents_modal_dialog_manager.h"
21 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
22 #include "grit/theme_resources.h"
23 #include "third_party/skia/include/core/SkColor.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/compositor/compositing_recorder.h"
27 #include "ui/gfx/canvas.h"
28 #include "ui/gfx/geometry/safe_integer_conversions.h"
29 #include "ui/gfx/paint_vector_icon.h"
30 #include "ui/gfx/vector_icons_public.h"
31 #include "ui/views/background.h"
32 #include "ui/views/controls/button/checkbox.h"
33 #include "ui/views/controls/combobox/combobox.h"
34 #include "ui/views/controls/image_view.h"
35 #include "ui/views/controls/label.h"
36 #include "ui/views/controls/link.h"
37 #include "ui/views/controls/throbber.h"
38 #include "ui/views/layout/box_layout.h"
39 #include "ui/views/widget/widget.h"
40 #include "ui/views/window/dialog_client_view.h"
42 namespace autofill {
44 // The number of pixels of blank space on the outer horizontal edges of the
45 // dialog.
46 const int kEdgePadding = 19;
48 SkColor kGreyTextColor = SkColorSetRGB(0x64, 0x64, 0x64);
50 CardUnmaskPromptView* CreateCardUnmaskPromptView(
51 CardUnmaskPromptController* controller,
52 content::WebContents* web_contents) {
53 return new CardUnmaskPromptViews(controller, web_contents);
56 CardUnmaskPromptViews::CardUnmaskPromptViews(
57 CardUnmaskPromptController* controller,
58 content::WebContents* web_contents)
59 : controller_(controller),
60 web_contents_(web_contents),
61 main_contents_(nullptr),
62 instructions_(nullptr),
63 permanent_error_label_(nullptr),
64 input_row_(nullptr),
65 cvc_input_(nullptr),
66 month_input_(nullptr),
67 year_input_(nullptr),
68 new_card_link_(nullptr),
69 error_icon_(nullptr),
70 error_label_(nullptr),
71 storage_row_(nullptr),
72 storage_checkbox_(nullptr),
73 progress_overlay_(nullptr),
74 progress_throbber_(nullptr),
75 progress_label_(nullptr),
76 overlay_animation_(this),
77 weak_ptr_factory_(this) {
80 CardUnmaskPromptViews::~CardUnmaskPromptViews() {
81 if (controller_)
82 controller_->OnUnmaskDialogClosed();
85 void CardUnmaskPromptViews::Show() {
86 constrained_window::ShowWebModalDialogViews(this, web_contents_);
89 void CardUnmaskPromptViews::ControllerGone() {
90 controller_ = nullptr;
91 ClosePrompt();
94 void CardUnmaskPromptViews::DisableAndWaitForVerification() {
95 SetInputsEnabled(false);
96 progress_overlay_->SetAlpha(0);
97 progress_overlay_->SetVisible(true);
98 progress_throbber_->Start();
99 overlay_animation_.Show();
100 GetDialogClientView()->UpdateDialogButtons();
101 Layout();
104 void CardUnmaskPromptViews::GotVerificationResult(
105 const base::string16& error_message,
106 bool allow_retry) {
107 progress_throbber_->Stop();
108 if (error_message.empty()) {
109 progress_label_->SetText(l10n_util::GetStringUTF16(
110 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_SUCCESS));
111 progress_throbber_->SetChecked(true);
112 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
113 FROM_HERE, base::Bind(&CardUnmaskPromptViews::ClosePrompt,
114 weak_ptr_factory_.GetWeakPtr()),
115 controller_->GetSuccessMessageDuration());
116 } else {
117 // TODO(estade): it's somewhat jarring when the error comes back too
118 // quickly.
119 overlay_animation_.Reset();
120 if (storage_row_)
121 storage_row_->SetAlpha(255);
122 progress_overlay_->SetVisible(false);
124 if (allow_retry) {
125 SetInputsEnabled(true);
127 if (!controller_->ShouldRequestExpirationDate()) {
128 // If there is more than one input showing, don't mark anything as
129 // invalid since we don't know the location of the problem.
130 cvc_input_->SetInvalid(true);
132 // Show a "New card?" link, which when clicked will cause us to ask
133 // for expiration date.
134 ShowNewCardLink();
137 // TODO(estade): When do we hide |error_label_|?
138 SetRetriableErrorMessage(error_message);
139 } else {
140 permanent_error_label_->SetText(error_message);
141 permanent_error_label_->SetVisible(true);
142 SetRetriableErrorMessage(base::string16());
144 GetDialogClientView()->UpdateDialogButtons();
147 Layout();
150 void CardUnmaskPromptViews::LinkClicked(views::Link* source, int event_flags) {
151 DCHECK_EQ(source, new_card_link_);
152 controller_->NewCardLinkClicked();
153 for (int i = 0; i < input_row_->child_count(); ++i)
154 input_row_->child_at(i)->SetVisible(true);
156 new_card_link_->SetVisible(false);
157 input_row_->InvalidateLayout();
158 cvc_input_->SetInvalid(false);
159 cvc_input_->SetText(base::string16());
160 GetDialogClientView()->UpdateDialogButtons();
161 GetWidget()->UpdateWindowTitle();
162 instructions_->SetText(controller_->GetInstructionsMessage());
163 SetRetriableErrorMessage(base::string16());
166 void CardUnmaskPromptViews::SetRetriableErrorMessage(
167 const base::string16& message) {
168 if (message.empty()) {
169 error_label_->SetMultiLine(false);
170 error_label_->SetText(base::ASCIIToUTF16(" "));
171 error_icon_->SetVisible(false);
172 } else {
173 error_label_->SetMultiLine(true);
174 error_label_->SetText(message);
175 error_icon_->SetVisible(true);
178 // Update the dialog's size.
179 if (GetWidget() && web_contents_) {
180 constrained_window::UpdateWebContentsModalDialogPosition(
181 GetWidget(),
182 web_modal::WebContentsModalDialogManager::FromWebContents(web_contents_)
183 ->delegate()
184 ->GetWebContentsModalDialogHost());
187 Layout();
190 void CardUnmaskPromptViews::SetInputsEnabled(bool enabled) {
191 cvc_input_->SetEnabled(enabled);
192 if (storage_checkbox_)
193 storage_checkbox_->SetEnabled(enabled);
194 month_input_->SetEnabled(enabled);
195 year_input_->SetEnabled(enabled);
198 void CardUnmaskPromptViews::ShowNewCardLink() {
199 if (new_card_link_)
200 return;
202 new_card_link_ = new views::Link(
203 l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_UNMASK_NEW_CARD_LINK));
204 new_card_link_->SetBorder(views::Border::CreateEmptyBorder(0, 7, 0, 0));
205 new_card_link_->SetUnderline(false);
206 new_card_link_->set_listener(this);
207 input_row_->AddChildView(new_card_link_);
210 views::View* CardUnmaskPromptViews::GetContentsView() {
211 InitIfNecessary();
212 return this;
215 views::View* CardUnmaskPromptViews::CreateFootnoteView() {
216 if (!controller_->CanStoreLocally())
217 return nullptr;
219 // Local storage checkbox and (?) tooltip.
220 storage_row_ = new FadeOutView();
221 views::BoxLayout* storage_row_layout = new views::BoxLayout(
222 views::BoxLayout::kHorizontal, kEdgePadding, kEdgePadding, 0);
223 storage_row_->SetLayoutManager(storage_row_layout);
224 storage_row_->SetBorder(
225 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
226 storage_row_->set_background(
227 views::Background::CreateSolidBackground(kLightShadingColor));
229 storage_checkbox_ = new views::Checkbox(l10n_util::GetStringUTF16(
230 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_CHECKBOX));
231 storage_checkbox_->SetChecked(controller_->GetStoreLocallyStartState());
232 storage_row_->AddChildView(storage_checkbox_);
233 storage_row_layout->SetFlexForView(storage_checkbox_, 1);
235 storage_row_->AddChildView(new TooltipIcon(l10n_util::GetStringUTF16(
236 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_TOOLTIP)));
238 return storage_row_;
241 gfx::Size CardUnmaskPromptViews::GetPreferredSize() const {
242 // Must hardcode a width so the label knows where to wrap.
243 const int kWidth = 375;
244 return gfx::Size(kWidth, GetHeightForWidth(kWidth));
247 void CardUnmaskPromptViews::Layout() {
248 gfx::Rect contents_bounds = GetContentsBounds();
249 main_contents_->SetBoundsRect(contents_bounds);
251 // The progress overlay extends from the top of the input row
252 // to the bottom of the content area.
253 gfx::RectF input_rect = gfx::RectF(input_row_->GetContentsBounds());
254 View::ConvertRectToTarget(input_row_, this, &input_rect);
255 input_rect.set_height(contents_bounds.height());
256 contents_bounds.Intersect(gfx::ToNearestRect(input_rect));
257 progress_overlay_->SetBoundsRect(contents_bounds);
260 int CardUnmaskPromptViews::GetHeightForWidth(int width) const {
261 if (!has_children())
262 return 0;
263 const gfx::Insets insets = GetInsets();
264 return main_contents_->GetHeightForWidth(width - insets.width()) +
265 insets.height();
268 void CardUnmaskPromptViews::OnNativeThemeChanged(const ui::NativeTheme* theme) {
269 SkColor bg_color =
270 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground);
271 progress_overlay_->set_background(
272 views::Background::CreateSolidBackground(bg_color));
273 progress_label_->SetBackgroundColor(bg_color);
274 progress_label_->SetEnabledColor(theme->GetSystemColor(
275 ui::NativeTheme::kColorId_ThrobberSpinningColor));
278 ui::ModalType CardUnmaskPromptViews::GetModalType() const {
279 return ui::MODAL_TYPE_CHILD;
282 base::string16 CardUnmaskPromptViews::GetWindowTitle() const {
283 return controller_->GetWindowTitle();
286 void CardUnmaskPromptViews::DeleteDelegate() {
287 delete this;
290 int CardUnmaskPromptViews::GetDialogButtons() const {
291 return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
294 base::string16 CardUnmaskPromptViews::GetDialogButtonLabel(
295 ui::DialogButton button) const {
296 if (button == ui::DIALOG_BUTTON_OK)
297 return l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_UNMASK_CONFIRM_BUTTON);
299 return DialogDelegateView::GetDialogButtonLabel(button);
302 bool CardUnmaskPromptViews::ShouldDefaultButtonBeBlue() const {
303 return true;
306 bool CardUnmaskPromptViews::IsDialogButtonEnabled(
307 ui::DialogButton button) const {
308 if (button == ui::DIALOG_BUTTON_CANCEL)
309 return true;
311 DCHECK_EQ(ui::DIALOG_BUTTON_OK, button);
313 return cvc_input_->enabled() &&
314 controller_->InputCvcIsValid(cvc_input_->text()) &&
315 ExpirationDateIsValid();
318 views::View* CardUnmaskPromptViews::GetInitiallyFocusedView() {
319 return cvc_input_;
322 bool CardUnmaskPromptViews::Cancel() {
323 return true;
326 bool CardUnmaskPromptViews::Accept() {
327 if (!controller_)
328 return true;
330 controller_->OnUnmaskResponse(
331 cvc_input_->text(),
332 month_input_->visible()
333 ? month_input_->GetTextForRow(month_input_->selected_index())
334 : base::string16(),
335 year_input_->visible()
336 ? year_input_->GetTextForRow(year_input_->selected_index())
337 : base::string16(),
338 storage_checkbox_ ? storage_checkbox_->checked() : false);
339 return false;
342 void CardUnmaskPromptViews::ContentsChanged(
343 views::Textfield* sender,
344 const base::string16& new_contents) {
345 if (controller_->InputCvcIsValid(new_contents))
346 cvc_input_->SetInvalid(false);
348 GetDialogClientView()->UpdateDialogButtons();
351 void CardUnmaskPromptViews::OnPerformAction(views::Combobox* combobox) {
352 if (ExpirationDateIsValid()) {
353 if (month_input_->invalid()) {
354 month_input_->SetInvalid(false);
355 year_input_->SetInvalid(false);
356 SetRetriableErrorMessage(base::string16());
358 } else if (month_input_->selected_index() !=
359 month_combobox_model_.GetDefaultIndex() &&
360 year_input_->selected_index() !=
361 year_combobox_model_.GetDefaultIndex()) {
362 month_input_->SetInvalid(true);
363 year_input_->SetInvalid(true);
364 SetRetriableErrorMessage(l10n_util::GetStringUTF16(
365 IDS_AUTOFILL_CARD_UNMASK_INVALID_EXPIRATION_DATE));
368 GetDialogClientView()->UpdateDialogButtons();
371 void CardUnmaskPromptViews::AnimationProgressed(
372 const gfx::Animation* animation) {
373 uint8_t alpha = static_cast<uint8_t>(animation->CurrentValueBetween(0, 255));
374 progress_overlay_->SetAlpha(alpha);
375 if (storage_row_)
376 storage_row_->SetAlpha(255 - alpha);
379 void CardUnmaskPromptViews::InitIfNecessary() {
380 if (has_children())
381 return;
383 main_contents_ = new views::View();
384 main_contents_->SetLayoutManager(
385 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 12));
386 AddChildView(main_contents_);
388 permanent_error_label_ = new views::Label();
389 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
390 permanent_error_label_->SetFontList(
391 rb.GetFontList(ui::ResourceBundle::BoldFont));
392 permanent_error_label_->set_background(
393 views::Background::CreateSolidBackground(kWarningColor));
394 permanent_error_label_->SetBorder(
395 views::Border::CreateEmptyBorder(12, kEdgePadding, 12, kEdgePadding));
396 permanent_error_label_->SetEnabledColor(SK_ColorWHITE);
397 permanent_error_label_->SetAutoColorReadabilityEnabled(false);
398 permanent_error_label_->SetVisible(false);
399 permanent_error_label_->SetMultiLine(true);
400 permanent_error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
401 main_contents_->AddChildView(permanent_error_label_);
403 views::View* controls_container = new views::View();
404 controls_container->SetLayoutManager(
405 new views::BoxLayout(views::BoxLayout::kVertical, kEdgePadding, 0, 0));
406 main_contents_->AddChildView(controls_container);
408 instructions_ = new views::Label(controller_->GetInstructionsMessage());
409 instructions_->SetEnabledColor(kGreyTextColor);
410 instructions_->SetMultiLine(true);
411 instructions_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
412 instructions_->SetBorder(views::Border::CreateEmptyBorder(0, 0, 16, 0));
413 controls_container->AddChildView(instructions_);
415 input_row_ = new views::View();
416 input_row_->SetLayoutManager(
417 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5));
418 controls_container->AddChildView(input_row_);
420 month_input_ = new views::Combobox(&month_combobox_model_);
421 month_input_->set_listener(this);
422 input_row_->AddChildView(month_input_);
423 views::Label* separator = new views::Label(l10n_util::GetStringUTF16(
424 IDS_AUTOFILL_CARD_UNMASK_EXPIRATION_DATE_SEPARATOR));
425 separator->SetEnabledColor(kGreyTextColor);
426 input_row_->AddChildView(separator);
427 year_input_ = new views::Combobox(&year_combobox_model_);
428 year_input_->set_listener(this);
429 input_row_->AddChildView(year_input_);
430 input_row_->AddChildView(new views::Label(base::ASCIIToUTF16(" ")));
431 // Hide all of the above as appropriate.
432 if (!controller_->ShouldRequestExpirationDate()) {
433 for (int i = 0; i < input_row_->child_count(); ++i)
434 input_row_->child_at(i)->SetVisible(false);
437 cvc_input_ = new DecoratedTextfield(
438 base::string16(),
439 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC), this);
440 cvc_input_->set_default_width_in_chars(8);
441 input_row_->AddChildView(cvc_input_);
443 views::ImageView* cvc_image = new views::ImageView();
444 cvc_image->SetImage(rb.GetImageSkiaNamed(controller_->GetCvcImageRid()));
445 input_row_->AddChildView(cvc_image);
447 views::View* temporary_error = new views::View();
448 views::BoxLayout* temporary_error_layout =
449 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 4);
450 temporary_error->SetLayoutManager(temporary_error_layout);
451 temporary_error_layout->set_cross_axis_alignment(
452 views::BoxLayout::CROSS_AXIS_ALIGNMENT_START);
453 temporary_error->SetBorder(views::Border::CreateEmptyBorder(8, 0, 0, 0));
454 controls_container->AddChildView(temporary_error);
456 error_icon_ = new views::ImageView();
457 error_icon_->SetVisible(false);
458 // TODO(estade): revisit this color.
459 error_icon_->SetImage(gfx::CreateVectorIcon(gfx::VectorIconId::WARNING, 16,
460 SkColorSetRGB(0xDB, 0x44, 0x37)));
461 temporary_error->AddChildView(error_icon_);
463 // Reserve vertical space for the error label, assuming it's one line.
464 error_label_ = new views::Label(base::ASCIIToUTF16(" "));
465 error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
466 error_label_->SetEnabledColor(kWarningColor);
467 temporary_error->AddChildView(error_label_);
468 temporary_error_layout->SetFlexForView(error_label_, 1);
470 progress_overlay_ = new FadeOutView();
471 progress_overlay_->set_fade_everything(true);
472 views::BoxLayout* progress_layout =
473 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5);
474 progress_layout->set_cross_axis_alignment(
475 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
476 progress_layout->set_main_axis_alignment(
477 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
478 progress_overlay_->SetLayoutManager(progress_layout);
480 progress_overlay_->SetVisible(false);
481 AddChildView(progress_overlay_);
483 progress_throbber_ = new views::Throbber();
484 progress_overlay_->AddChildView(progress_throbber_);
486 progress_label_ = new views::Label(l10n_util::GetStringUTF16(
487 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_IN_PROGRESS));
488 progress_overlay_->AddChildView(progress_label_);
491 bool CardUnmaskPromptViews::ExpirationDateIsValid() const {
492 if (!controller_->ShouldRequestExpirationDate())
493 return true;
495 return controller_->InputExpirationIsValid(
496 month_input_->GetTextForRow(month_input_->selected_index()),
497 year_input_->GetTextForRow(year_input_->selected_index()));
500 void CardUnmaskPromptViews::ClosePrompt() {
501 GetWidget()->Close();
504 CardUnmaskPromptViews::FadeOutView::FadeOutView()
505 : fade_everything_(false), alpha_(255) {
507 CardUnmaskPromptViews::FadeOutView::~FadeOutView() {
510 void CardUnmaskPromptViews::FadeOutView::PaintChildren(
511 const ui::PaintContext& context) {
512 ui::CompositingRecorder recorder(context, alpha_);
513 views::View::PaintChildren(context);
516 void CardUnmaskPromptViews::FadeOutView::OnPaint(gfx::Canvas* canvas) {
517 if (!fade_everything_ || alpha_ == 255)
518 return views::View::OnPaint(canvas);
520 canvas->SaveLayerAlpha(alpha_);
521 views::View::OnPaint(canvas);
522 canvas->Restore();
525 void CardUnmaskPromptViews::FadeOutView::SetAlpha(uint8_t alpha) {
526 alpha_ = alpha;
527 SchedulePaint();
530 } // namespace autofill