| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ui/views/autofill/card_unmask_prompt_views.h" |
| 6 |
| 5 #include "base/basictypes.h" | 7 #include "base/basictypes.h" |
| 6 #include "base/strings/utf_string_conversions.h" | 8 #include "base/strings/utf_string_conversions.h" |
| 7 #include "chrome/browser/ui/autofill/autofill_dialog_models.h" | |
| 8 #include "chrome/browser/ui/autofill/autofill_dialog_types.h" | 9 #include "chrome/browser/ui/autofill/autofill_dialog_types.h" |
| 9 #include "chrome/browser/ui/autofill/card_unmask_prompt_controller.h" | 10 #include "chrome/browser/ui/autofill/card_unmask_prompt_controller.h" |
| 10 #include "chrome/browser/ui/autofill/card_unmask_prompt_view.h" | |
| 11 #include "chrome/browser/ui/views/autofill/decorated_textfield.h" | 11 #include "chrome/browser/ui/views/autofill/decorated_textfield.h" |
| 12 #include "chrome/browser/ui/views/autofill/tooltip_icon.h" | 12 #include "chrome/browser/ui/views/autofill/tooltip_icon.h" |
| 13 #include "chrome/grit/generated_resources.h" | 13 #include "chrome/grit/generated_resources.h" |
| 14 #include "components/constrained_window/constrained_window_views.h" | 14 #include "components/constrained_window/constrained_window_views.h" |
| 15 #include "components/web_modal/web_contents_modal_dialog_host.h" | 15 #include "components/web_modal/web_contents_modal_dialog_host.h" |
| 16 #include "components/web_modal/web_contents_modal_dialog_manager.h" | 16 #include "components/web_modal/web_contents_modal_dialog_manager.h" |
| 17 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" | 17 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" |
| 18 #include "grit/theme_resources.h" | 18 #include "grit/theme_resources.h" |
| 19 #include "third_party/skia/include/core/SkColor.h" | 19 #include "third_party/skia/include/core/SkColor.h" |
| 20 #include "ui/base/l10n/l10n_util.h" | 20 #include "ui/base/l10n/l10n_util.h" |
| 21 #include "ui/base/resource/resource_bundle.h" | 21 #include "ui/base/resource/resource_bundle.h" |
| 22 #include "ui/views/background.h" | 22 #include "ui/views/background.h" |
| 23 #include "ui/views/controls/button/checkbox.h" | 23 #include "ui/views/controls/button/checkbox.h" |
| 24 #include "ui/views/controls/combobox/combobox.h" | 24 #include "ui/views/controls/combobox/combobox.h" |
| 25 #include "ui/views/controls/combobox/combobox_listener.h" | |
| 26 #include "ui/views/controls/image_view.h" | 25 #include "ui/views/controls/image_view.h" |
| 27 #include "ui/views/controls/label.h" | 26 #include "ui/views/controls/label.h" |
| 28 #include "ui/views/controls/textfield/textfield_controller.h" | |
| 29 #include "ui/views/layout/box_layout.h" | 27 #include "ui/views/layout/box_layout.h" |
| 30 #include "ui/views/widget/widget.h" | 28 #include "ui/views/widget/widget.h" |
| 31 #include "ui/views/window/dialog_client_view.h" | 29 #include "ui/views/window/dialog_client_view.h" |
| 32 #include "ui/views/window/dialog_delegate.h" | |
| 33 | 30 |
| 34 namespace autofill { | 31 namespace autofill { |
| 35 | 32 |
| 36 namespace { | |
| 37 | |
| 38 // The number of pixels of blank space on the outer horizontal edges of the | 33 // The number of pixels of blank space on the outer horizontal edges of the |
| 39 // dialog. | 34 // dialog. |
| 40 const int kEdgePadding = 19; | 35 const int kEdgePadding = 19; |
| 41 | 36 |
| 42 // From AutofillDialogViews. TODO(estade): share. | 37 // From AutofillDialogViews. TODO(estade): share. |
| 43 SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0); | 38 SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0); |
| 44 SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0); | 39 SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0); |
| 45 | 40 |
| 46 class CardUnmaskPromptViews : public CardUnmaskPromptView, | |
| 47 views::ComboboxListener, | |
| 48 views::DialogDelegateView, | |
| 49 views::TextfieldController { | |
| 50 public: | |
| 51 explicit CardUnmaskPromptViews(CardUnmaskPromptController* controller) | |
| 52 : controller_(controller), | |
| 53 main_contents_(nullptr), | |
| 54 permanent_error_label_(nullptr), | |
| 55 cvc_input_(nullptr), | |
| 56 month_input_(nullptr), | |
| 57 year_input_(nullptr), | |
| 58 error_label_(nullptr), | |
| 59 storage_checkbox_(nullptr), | |
| 60 progress_overlay_(nullptr), | |
| 61 progress_label_(nullptr), | |
| 62 weak_ptr_factory_(this) {} | |
| 63 | |
| 64 ~CardUnmaskPromptViews() override { | |
| 65 if (controller_) | |
| 66 controller_->OnUnmaskDialogClosed(); | |
| 67 } | |
| 68 | |
| 69 void Show() { | |
| 70 constrained_window::ShowWebModalDialogViews(this, | |
| 71 controller_->GetWebContents()); | |
| 72 } | |
| 73 | |
| 74 // CardUnmaskPromptView | |
| 75 void ControllerGone() override { | |
| 76 controller_ = nullptr; | |
| 77 ClosePrompt(); | |
| 78 } | |
| 79 | |
| 80 void DisableAndWaitForVerification() override { | |
| 81 SetInputsEnabled(false); | |
| 82 progress_overlay_->SetVisible(true); | |
| 83 GetDialogClientView()->UpdateDialogButtons(); | |
| 84 Layout(); | |
| 85 } | |
| 86 | |
| 87 void GotVerificationResult(const base::string16& error_message, | |
| 88 bool allow_retry) override { | |
| 89 if (error_message.empty()) { | |
| 90 progress_label_->SetText(l10n_util::GetStringUTF16( | |
| 91 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_SUCCESS)); | |
| 92 base::MessageLoop::current()->PostDelayedTask( | |
| 93 FROM_HERE, base::Bind(&CardUnmaskPromptViews::ClosePrompt, | |
| 94 weak_ptr_factory_.GetWeakPtr()), | |
| 95 base::TimeDelta::FromSeconds(1)); | |
| 96 } else { | |
| 97 // TODO(estade): it's somewhat jarring when the error comes back too | |
| 98 // quickly. | |
| 99 progress_overlay_->SetVisible(false); | |
| 100 | |
| 101 if (allow_retry) { | |
| 102 SetInputsEnabled(true); | |
| 103 | |
| 104 // If there is more than one input showing, don't mark anything as | |
| 105 // invalid since we don't know the location of the problem. | |
| 106 if (!controller_->ShouldRequestExpirationDate()) | |
| 107 cvc_input_->SetInvalid(true); | |
| 108 | |
| 109 // TODO(estade): When do we hide |error_label_|? | |
| 110 SetRetriableErrorMessage(error_message); | |
| 111 } else { | |
| 112 permanent_error_label_->SetText(error_message); | |
| 113 permanent_error_label_->SetVisible(true); | |
| 114 SetRetriableErrorMessage(base::string16()); | |
| 115 } | |
| 116 | |
| 117 GetDialogClientView()->UpdateDialogButtons(); | |
| 118 } | |
| 119 | |
| 120 Layout(); | |
| 121 } | |
| 122 | |
| 123 void SetRetriableErrorMessage(const base::string16& message) { | |
| 124 if (message.empty()) { | |
| 125 error_label_->SetMultiLine(false); | |
| 126 error_label_->SetText(base::ASCIIToUTF16(" ")); | |
| 127 } else { | |
| 128 error_label_->SetMultiLine(true); | |
| 129 error_label_->SetText(message); | |
| 130 } | |
| 131 | |
| 132 // Update the dialog's size. | |
| 133 if (GetWidget() && controller_->GetWebContents()) { | |
| 134 constrained_window::UpdateWebContentsModalDialogPosition( | |
| 135 GetWidget(), | |
| 136 web_modal::WebContentsModalDialogManager::FromWebContents( | |
| 137 controller_->GetWebContents()) | |
| 138 ->delegate() | |
| 139 ->GetWebContentsModalDialogHost()); | |
| 140 } | |
| 141 } | |
| 142 | |
| 143 void SetInputsEnabled(bool enabled) { | |
| 144 cvc_input_->SetEnabled(enabled); | |
| 145 storage_checkbox_->SetEnabled(enabled); | |
| 146 | |
| 147 if (month_input_) | |
| 148 month_input_->SetEnabled(enabled); | |
| 149 if (year_input_) | |
| 150 year_input_->SetEnabled(enabled); | |
| 151 } | |
| 152 | |
| 153 // views::DialogDelegateView | |
| 154 View* GetContentsView() override { | |
| 155 InitIfNecessary(); | |
| 156 return this; | |
| 157 } | |
| 158 | |
| 159 views::View* CreateFootnoteView() override { | |
| 160 // Local storage checkbox and (?) tooltip. | |
| 161 views::View* storage_row = new views::View(); | |
| 162 views::BoxLayout* storage_row_layout = new views::BoxLayout( | |
| 163 views::BoxLayout::kHorizontal, kEdgePadding, kEdgePadding, 0); | |
| 164 storage_row->SetLayoutManager(storage_row_layout); | |
| 165 storage_row->SetBorder( | |
| 166 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor)); | |
| 167 storage_row->set_background( | |
| 168 views::Background::CreateSolidBackground(kShadingColor)); | |
| 169 | |
| 170 storage_checkbox_ = new views::Checkbox(l10n_util::GetStringUTF16( | |
| 171 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_CHECKBOX)); | |
| 172 storage_checkbox_->SetChecked(controller_->GetStoreLocallyStartState()); | |
| 173 storage_row->AddChildView(storage_checkbox_); | |
| 174 storage_row_layout->SetFlexForView(storage_checkbox_, 1); | |
| 175 | |
| 176 storage_row->AddChildView(new TooltipIcon(l10n_util::GetStringUTF16( | |
| 177 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_TOOLTIP))); | |
| 178 | |
| 179 return storage_row; | |
| 180 } | |
| 181 | |
| 182 // views::View | |
| 183 gfx::Size GetPreferredSize() const override { | |
| 184 // Must hardcode a width so the label knows where to wrap. TODO(estade): | |
| 185 // This can lead to a weird looking dialog if we end up getting allocated | |
| 186 // more width than we ask for, e.g. if the title is super long. | |
| 187 const int kWidth = 375; | |
| 188 return gfx::Size(kWidth, GetHeightForWidth(kWidth)); | |
| 189 } | |
| 190 | |
| 191 void Layout() override { | |
| 192 for (int i = 0; i < child_count(); ++i) { | |
| 193 child_at(i)->SetBoundsRect(GetContentsBounds()); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 int GetHeightForWidth(int width) const override { | |
| 198 if (!has_children()) | |
| 199 return 0; | |
| 200 const gfx::Insets insets = GetInsets(); | |
| 201 return main_contents_->GetHeightForWidth(width - insets.width()) + | |
| 202 insets.height(); | |
| 203 } | |
| 204 | |
| 205 void OnNativeThemeChanged(const ui::NativeTheme* theme) override { | |
| 206 SkColor bg_color = | |
| 207 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground); | |
| 208 bg_color = SkColorSetA(bg_color, 0xDD); | |
| 209 progress_overlay_->set_background( | |
| 210 views::Background::CreateSolidBackground(bg_color)); | |
| 211 } | |
| 212 | |
| 213 ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_CHILD; } | |
| 214 | |
| 215 base::string16 GetWindowTitle() const override { | |
| 216 return controller_->GetWindowTitle(); | |
| 217 } | |
| 218 | |
| 219 void DeleteDelegate() override { delete this; } | |
| 220 | |
| 221 int GetDialogButtons() const override { | |
| 222 return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL; | |
| 223 } | |
| 224 | |
| 225 base::string16 GetDialogButtonLabel(ui::DialogButton button) const override { | |
| 226 if (button == ui::DIALOG_BUTTON_OK) | |
| 227 return l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_UNMASK_CONFIRM_BUTTON); | |
| 228 | |
| 229 return DialogDelegateView::GetDialogButtonLabel(button); | |
| 230 } | |
| 231 | |
| 232 bool ShouldDefaultButtonBeBlue() const override { return true; } | |
| 233 | |
| 234 bool IsDialogButtonEnabled(ui::DialogButton button) const override { | |
| 235 if (button == ui::DIALOG_BUTTON_CANCEL) | |
| 236 return true; | |
| 237 | |
| 238 DCHECK_EQ(ui::DIALOG_BUTTON_OK, button); | |
| 239 | |
| 240 return cvc_input_->enabled() && | |
| 241 controller_->InputCvcIsValid(cvc_input_->text()) && | |
| 242 ExpirationDateIsValid(); | |
| 243 } | |
| 244 | |
| 245 views::View* GetInitiallyFocusedView() override { return cvc_input_; } | |
| 246 | |
| 247 bool Cancel() override { | |
| 248 return true; | |
| 249 } | |
| 250 | |
| 251 bool Accept() override { | |
| 252 if (!controller_) | |
| 253 return true; | |
| 254 | |
| 255 controller_->OnUnmaskResponse( | |
| 256 cvc_input_->text(), | |
| 257 month_input_ | |
| 258 ? month_input_->GetTextForRow(month_input_->selected_index()) | |
| 259 : base::string16(), | |
| 260 year_input_ ? year_input_->GetTextForRow(year_input_->selected_index()) | |
| 261 : base::string16(), | |
| 262 storage_checkbox_ ? storage_checkbox_->checked() : false); | |
| 263 return false; | |
| 264 } | |
| 265 | |
| 266 // views::TextfieldController | |
| 267 void ContentsChanged(views::Textfield* sender, | |
| 268 const base::string16& new_contents) override { | |
| 269 if (controller_->InputCvcIsValid(new_contents)) | |
| 270 cvc_input_->SetInvalid(false); | |
| 271 | |
| 272 GetDialogClientView()->UpdateDialogButtons(); | |
| 273 } | |
| 274 | |
| 275 // views::ComboboxListener | |
| 276 void OnPerformAction(views::Combobox* combobox) override { | |
| 277 if (ExpirationDateIsValid()) { | |
| 278 if (month_input_->invalid()) { | |
| 279 month_input_->SetInvalid(false); | |
| 280 year_input_->SetInvalid(false); | |
| 281 error_label_->SetMultiLine(false); | |
| 282 SetRetriableErrorMessage(base::string16()); | |
| 283 } | |
| 284 } else if (month_input_->selected_index() != | |
| 285 month_combobox_model_.GetDefaultIndex() && | |
| 286 year_input_->selected_index() != | |
| 287 year_combobox_model_.GetDefaultIndex()) { | |
| 288 month_input_->SetInvalid(true); | |
| 289 year_input_->SetInvalid(true); | |
| 290 error_label_->SetMultiLine(true); | |
| 291 SetRetriableErrorMessage(l10n_util::GetStringUTF16( | |
| 292 IDS_AUTOFILL_CARD_UNMASK_INVALID_EXPIRATION_DATE)); | |
| 293 } | |
| 294 | |
| 295 GetDialogClientView()->UpdateDialogButtons(); | |
| 296 } | |
| 297 | |
| 298 private: | |
| 299 void InitIfNecessary() { | |
| 300 if (has_children()) | |
| 301 return; | |
| 302 | |
| 303 main_contents_ = new views::View(); | |
| 304 main_contents_->SetLayoutManager( | |
| 305 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 10)); | |
| 306 AddChildView(main_contents_); | |
| 307 | |
| 308 permanent_error_label_ = new views::Label(); | |
| 309 permanent_error_label_->set_background( | |
| 310 views::Background::CreateSolidBackground( | |
| 311 SkColorSetRGB(0xdb, 0x44, 0x37))); | |
| 312 permanent_error_label_->SetBorder( | |
| 313 views::Border::CreateEmptyBorder(10, kEdgePadding, 10, kEdgePadding)); | |
| 314 permanent_error_label_->SetEnabledColor(SK_ColorWHITE); | |
| 315 permanent_error_label_->SetAutoColorReadabilityEnabled(false); | |
| 316 permanent_error_label_->SetVisible(false); | |
| 317 permanent_error_label_->SetMultiLine(true); | |
| 318 permanent_error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 319 main_contents_->AddChildView(permanent_error_label_); | |
| 320 | |
| 321 views::View* controls_container = new views::View(); | |
| 322 controls_container->SetLayoutManager( | |
| 323 new views::BoxLayout(views::BoxLayout::kVertical, kEdgePadding, 0, 0)); | |
| 324 main_contents_->AddChildView(controls_container); | |
| 325 | |
| 326 views::Label* instructions = | |
| 327 new views::Label(controller_->GetInstructionsMessage()); | |
| 328 | |
| 329 instructions->SetMultiLine(true); | |
| 330 instructions->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 331 instructions->SetBorder(views::Border::CreateEmptyBorder(0, 0, 15, 0)); | |
| 332 controls_container->AddChildView(instructions); | |
| 333 | |
| 334 views::View* input_row = new views::View(); | |
| 335 input_row->SetLayoutManager( | |
| 336 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5)); | |
| 337 controls_container->AddChildView(input_row); | |
| 338 | |
| 339 if (controller_->ShouldRequestExpirationDate()) { | |
| 340 month_input_ = new views::Combobox(&month_combobox_model_); | |
| 341 month_input_->set_listener(this); | |
| 342 input_row->AddChildView(month_input_); | |
| 343 input_row->AddChildView(new views::Label(l10n_util::GetStringUTF16( | |
| 344 IDS_AUTOFILL_CARD_UNMASK_EXPIRATION_DATE_SEPARATOR))); | |
| 345 year_input_ = new views::Combobox(&year_combobox_model_); | |
| 346 year_input_->set_listener(this); | |
| 347 input_row->AddChildView(year_input_); | |
| 348 | |
| 349 input_row->AddChildView(new views::Label(base::ASCIIToUTF16(" "))); | |
| 350 } | |
| 351 | |
| 352 cvc_input_ = new DecoratedTextfield( | |
| 353 base::string16(), | |
| 354 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC), | |
| 355 this); | |
| 356 cvc_input_->set_default_width_in_chars(8); | |
| 357 input_row->AddChildView(cvc_input_); | |
| 358 | |
| 359 views::ImageView* cvc_image = new views::ImageView(); | |
| 360 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 361 cvc_image->SetImage(rb.GetImageSkiaNamed(controller_->GetCvcImageRid())); | |
| 362 input_row->AddChildView(cvc_image); | |
| 363 | |
| 364 // Reserve vertical space for the error label, assuming it's one line. | |
| 365 error_label_ = new views::Label(base::ASCIIToUTF16(" ")); | |
| 366 error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 367 error_label_->SetEnabledColor(kWarningColor); | |
| 368 error_label_->SetBorder(views::Border::CreateEmptyBorder(3, 0, 5, 0)); | |
| 369 controls_container->AddChildView(error_label_); | |
| 370 | |
| 371 progress_overlay_ = new views::View(); | |
| 372 views::BoxLayout* progress_layout = | |
| 373 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); | |
| 374 progress_layout->set_main_axis_alignment( | |
| 375 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER); | |
| 376 progress_layout->set_cross_axis_alignment( | |
| 377 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); | |
| 378 progress_overlay_->SetLayoutManager(progress_layout); | |
| 379 | |
| 380 progress_overlay_->SetVisible(false); | |
| 381 AddChildView(progress_overlay_); | |
| 382 | |
| 383 progress_label_ = new views::Label(l10n_util::GetStringUTF16( | |
| 384 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_IN_PROGRESS)); | |
| 385 progress_overlay_->AddChildView(progress_label_); | |
| 386 } | |
| 387 | |
| 388 void ClosePrompt() { GetWidget()->Close(); } | |
| 389 | |
| 390 bool ExpirationDateIsValid() const { | |
| 391 if (!controller_->ShouldRequestExpirationDate()) | |
| 392 return true; | |
| 393 | |
| 394 return controller_->InputExpirationIsValid( | |
| 395 month_input_->GetTextForRow(month_input_->selected_index()), | |
| 396 year_input_->GetTextForRow(year_input_->selected_index())); | |
| 397 } | |
| 398 | |
| 399 CardUnmaskPromptController* controller_; | |
| 400 | |
| 401 views::View* main_contents_; | |
| 402 | |
| 403 // The error label for permanent errors (where the user can't retry). | |
| 404 views::Label* permanent_error_label_; | |
| 405 | |
| 406 DecoratedTextfield* cvc_input_; | |
| 407 | |
| 408 // These will be null when expiration date is not required. | |
| 409 views::Combobox* month_input_; | |
| 410 views::Combobox* year_input_; | |
| 411 | |
| 412 MonthComboboxModel month_combobox_model_; | |
| 413 YearComboboxModel year_combobox_model_; | |
| 414 | |
| 415 // The error label for most errors, which lives beneath the inputs. | |
| 416 views::Label* error_label_; | |
| 417 | |
| 418 views::Checkbox* storage_checkbox_; | |
| 419 | |
| 420 views::View* progress_overlay_; | |
| 421 views::Label* progress_label_; | |
| 422 | |
| 423 base::WeakPtrFactory<CardUnmaskPromptViews> weak_ptr_factory_; | |
| 424 | |
| 425 DISALLOW_COPY_AND_ASSIGN(CardUnmaskPromptViews); | |
| 426 }; | |
| 427 | |
| 428 } // namespace | |
| 429 | |
| 430 // static | 41 // static |
| 431 CardUnmaskPromptView* CardUnmaskPromptView::CreateAndShow( | 42 CardUnmaskPromptView* CardUnmaskPromptView::CreateAndShow( |
| 432 CardUnmaskPromptController* controller) { | 43 CardUnmaskPromptController* controller) { |
| 433 CardUnmaskPromptViews* view = new CardUnmaskPromptViews(controller); | 44 CardUnmaskPromptViews* view = new CardUnmaskPromptViews(controller); |
| 434 view->Show(); | 45 view->Show(); |
| 435 return view; | 46 return view; |
| 436 } | 47 } |
| 437 | 48 |
| 49 CardUnmaskPromptViews::CardUnmaskPromptViews( |
| 50 CardUnmaskPromptController* controller) |
| 51 : controller_(controller), |
| 52 main_contents_(nullptr), |
| 53 permanent_error_label_(nullptr), |
| 54 cvc_input_(nullptr), |
| 55 month_input_(nullptr), |
| 56 year_input_(nullptr), |
| 57 error_label_(nullptr), |
| 58 storage_checkbox_(nullptr), |
| 59 progress_overlay_(nullptr), |
| 60 progress_label_(nullptr), |
| 61 weak_ptr_factory_(this) { |
| 62 } |
| 63 |
| 64 CardUnmaskPromptViews::~CardUnmaskPromptViews() { |
| 65 if (controller_) |
| 66 controller_->OnUnmaskDialogClosed(); |
| 67 } |
| 68 |
| 69 void CardUnmaskPromptViews::Show() { |
| 70 constrained_window::ShowWebModalDialogViews(this, |
| 71 controller_->GetWebContents()); |
| 72 } |
| 73 |
| 74 void CardUnmaskPromptViews::ControllerGone() { |
| 75 controller_ = nullptr; |
| 76 ClosePrompt(); |
| 77 } |
| 78 |
| 79 void CardUnmaskPromptViews::DisableAndWaitForVerification() { |
| 80 SetInputsEnabled(false); |
| 81 progress_overlay_->SetVisible(true); |
| 82 GetDialogClientView()->UpdateDialogButtons(); |
| 83 Layout(); |
| 84 } |
| 85 |
| 86 void CardUnmaskPromptViews::GotVerificationResult( |
| 87 const base::string16& error_message, |
| 88 bool allow_retry) { |
| 89 if (error_message.empty()) { |
| 90 progress_label_->SetText(l10n_util::GetStringUTF16( |
| 91 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_SUCCESS)); |
| 92 base::MessageLoop::current()->PostDelayedTask( |
| 93 FROM_HERE, base::Bind(&CardUnmaskPromptViews::ClosePrompt, |
| 94 weak_ptr_factory_.GetWeakPtr()), |
| 95 controller_->GetSuccessMessageDuration()); |
| 96 } else { |
| 97 // TODO(estade): it's somewhat jarring when the error comes back too |
| 98 // quickly. |
| 99 progress_overlay_->SetVisible(false); |
| 100 |
| 101 if (allow_retry) { |
| 102 SetInputsEnabled(true); |
| 103 |
| 104 // If there is more than one input showing, don't mark anything as |
| 105 // invalid since we don't know the location of the problem. |
| 106 if (!controller_->ShouldRequestExpirationDate()) |
| 107 cvc_input_->SetInvalid(true); |
| 108 |
| 109 // TODO(estade): When do we hide |error_label_|? |
| 110 SetRetriableErrorMessage(error_message); |
| 111 } else { |
| 112 permanent_error_label_->SetText(error_message); |
| 113 permanent_error_label_->SetVisible(true); |
| 114 SetRetriableErrorMessage(base::string16()); |
| 115 } |
| 116 GetDialogClientView()->UpdateDialogButtons(); |
| 117 } |
| 118 |
| 119 Layout(); |
| 120 } |
| 121 |
| 122 void CardUnmaskPromptViews::SetRetriableErrorMessage( |
| 123 const base::string16& message) { |
| 124 if (message.empty()) { |
| 125 error_label_->SetMultiLine(false); |
| 126 error_label_->SetText(base::ASCIIToUTF16(" ")); |
| 127 } else { |
| 128 error_label_->SetMultiLine(true); |
| 129 error_label_->SetText(message); |
| 130 } |
| 131 |
| 132 // Update the dialog's size. |
| 133 if (GetWidget() && controller_->GetWebContents()) { |
| 134 constrained_window::UpdateWebContentsModalDialogPosition( |
| 135 GetWidget(), web_modal::WebContentsModalDialogManager::FromWebContents( |
| 136 controller_->GetWebContents()) |
| 137 ->delegate() |
| 138 ->GetWebContentsModalDialogHost()); |
| 139 } |
| 140 } |
| 141 |
| 142 void CardUnmaskPromptViews::SetInputsEnabled(bool enabled) { |
| 143 cvc_input_->SetEnabled(enabled); |
| 144 storage_checkbox_->SetEnabled(enabled); |
| 145 |
| 146 if (month_input_) |
| 147 month_input_->SetEnabled(enabled); |
| 148 if (year_input_) |
| 149 year_input_->SetEnabled(enabled); |
| 150 } |
| 151 |
| 152 views::View* CardUnmaskPromptViews::GetContentsView() { |
| 153 InitIfNecessary(); |
| 154 return this; |
| 155 } |
| 156 |
| 157 views::View* CardUnmaskPromptViews::CreateFootnoteView() { |
| 158 // Local storage checkbox and (?) tooltip. |
| 159 views::View* storage_row = new views::View(); |
| 160 views::BoxLayout* storage_row_layout = new views::BoxLayout( |
| 161 views::BoxLayout::kHorizontal, kEdgePadding, kEdgePadding, 0); |
| 162 storage_row->SetLayoutManager(storage_row_layout); |
| 163 storage_row->SetBorder( |
| 164 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor)); |
| 165 storage_row->set_background( |
| 166 views::Background::CreateSolidBackground(kShadingColor)); |
| 167 |
| 168 storage_checkbox_ = new views::Checkbox(l10n_util::GetStringUTF16( |
| 169 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_CHECKBOX)); |
| 170 storage_checkbox_->SetChecked(controller_->GetStoreLocallyStartState()); |
| 171 storage_row->AddChildView(storage_checkbox_); |
| 172 storage_row_layout->SetFlexForView(storage_checkbox_, 1); |
| 173 |
| 174 storage_row->AddChildView(new TooltipIcon(l10n_util::GetStringUTF16( |
| 175 IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_TOOLTIP))); |
| 176 |
| 177 return storage_row; |
| 178 } |
| 179 |
| 180 gfx::Size CardUnmaskPromptViews::GetPreferredSize() const { |
| 181 // Must hardcode a width so the label knows where to wrap. TODO(estade): |
| 182 // This can lead to a weird looking dialog if we end up getting allocated |
| 183 // more width than we ask for, e.g. if the title is super long. |
| 184 const int kWidth = 375; |
| 185 return gfx::Size(kWidth, GetHeightForWidth(kWidth)); |
| 186 } |
| 187 |
| 188 void CardUnmaskPromptViews::Layout() { |
| 189 for (int i = 0; i < child_count(); ++i) { |
| 190 child_at(i)->SetBoundsRect(GetContentsBounds()); |
| 191 } |
| 192 } |
| 193 |
| 194 int CardUnmaskPromptViews::GetHeightForWidth(int width) const { |
| 195 if (!has_children()) |
| 196 return 0; |
| 197 const gfx::Insets insets = GetInsets(); |
| 198 return main_contents_->GetHeightForWidth(width - insets.width()) + |
| 199 insets.height(); |
| 200 } |
| 201 |
| 202 void CardUnmaskPromptViews::OnNativeThemeChanged(const ui::NativeTheme* theme) { |
| 203 SkColor bg_color = |
| 204 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground); |
| 205 bg_color = SkColorSetA(bg_color, 0xDD); |
| 206 progress_overlay_->set_background( |
| 207 views::Background::CreateSolidBackground(bg_color)); |
| 208 } |
| 209 |
| 210 ui::ModalType CardUnmaskPromptViews::GetModalType() const { |
| 211 return ui::MODAL_TYPE_CHILD; |
| 212 } |
| 213 |
| 214 base::string16 CardUnmaskPromptViews::GetWindowTitle() const { |
| 215 return controller_->GetWindowTitle(); |
| 216 } |
| 217 |
| 218 void CardUnmaskPromptViews::DeleteDelegate() { |
| 219 delete this; |
| 220 } |
| 221 |
| 222 int CardUnmaskPromptViews::GetDialogButtons() const { |
| 223 return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL; |
| 224 } |
| 225 |
| 226 base::string16 CardUnmaskPromptViews::GetDialogButtonLabel( |
| 227 ui::DialogButton button) const { |
| 228 if (button == ui::DIALOG_BUTTON_OK) |
| 229 return l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_UNMASK_CONFIRM_BUTTON); |
| 230 |
| 231 return DialogDelegateView::GetDialogButtonLabel(button); |
| 232 } |
| 233 |
| 234 bool CardUnmaskPromptViews::ShouldDefaultButtonBeBlue() const { |
| 235 return true; |
| 236 } |
| 237 |
| 238 bool CardUnmaskPromptViews::IsDialogButtonEnabled( |
| 239 ui::DialogButton button) const { |
| 240 if (button == ui::DIALOG_BUTTON_CANCEL) |
| 241 return true; |
| 242 |
| 243 DCHECK_EQ(ui::DIALOG_BUTTON_OK, button); |
| 244 |
| 245 return cvc_input_->enabled() && |
| 246 controller_->InputCvcIsValid(cvc_input_->text()) && |
| 247 ExpirationDateIsValid(); |
| 248 } |
| 249 |
| 250 views::View* CardUnmaskPromptViews::GetInitiallyFocusedView() { |
| 251 return cvc_input_; |
| 252 } |
| 253 |
| 254 bool CardUnmaskPromptViews::Cancel() { |
| 255 return true; |
| 256 } |
| 257 |
| 258 bool CardUnmaskPromptViews::Accept() { |
| 259 if (!controller_) |
| 260 return true; |
| 261 |
| 262 controller_->OnUnmaskResponse( |
| 263 cvc_input_->text(), |
| 264 month_input_ ? month_input_->GetTextForRow(month_input_->selected_index()) |
| 265 : base::string16(), |
| 266 year_input_ ? year_input_->GetTextForRow(year_input_->selected_index()) |
| 267 : base::string16(), |
| 268 storage_checkbox_ ? storage_checkbox_->checked() : false); |
| 269 return false; |
| 270 } |
| 271 |
| 272 void CardUnmaskPromptViews::ContentsChanged( |
| 273 views::Textfield* sender, |
| 274 const base::string16& new_contents) { |
| 275 if (controller_->InputCvcIsValid(new_contents)) |
| 276 cvc_input_->SetInvalid(false); |
| 277 |
| 278 GetDialogClientView()->UpdateDialogButtons(); |
| 279 } |
| 280 |
| 281 void CardUnmaskPromptViews::OnPerformAction(views::Combobox* combobox) { |
| 282 if (ExpirationDateIsValid()) { |
| 283 if (month_input_->invalid()) { |
| 284 month_input_->SetInvalid(false); |
| 285 year_input_->SetInvalid(false); |
| 286 error_label_->SetMultiLine(false); |
| 287 SetRetriableErrorMessage(base::string16()); |
| 288 } |
| 289 } else if (month_input_->selected_index() != |
| 290 month_combobox_model_.GetDefaultIndex() && |
| 291 year_input_->selected_index() != |
| 292 year_combobox_model_.GetDefaultIndex()) { |
| 293 month_input_->SetInvalid(true); |
| 294 year_input_->SetInvalid(true); |
| 295 error_label_->SetMultiLine(true); |
| 296 SetRetriableErrorMessage(l10n_util::GetStringUTF16( |
| 297 IDS_AUTOFILL_CARD_UNMASK_INVALID_EXPIRATION_DATE)); |
| 298 } |
| 299 |
| 300 GetDialogClientView()->UpdateDialogButtons(); |
| 301 } |
| 302 |
| 303 void CardUnmaskPromptViews::InitIfNecessary() { |
| 304 if (has_children()) |
| 305 return; |
| 306 |
| 307 main_contents_ = new views::View(); |
| 308 main_contents_->SetLayoutManager( |
| 309 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 10)); |
| 310 AddChildView(main_contents_); |
| 311 |
| 312 permanent_error_label_ = new views::Label(); |
| 313 permanent_error_label_->set_background( |
| 314 views::Background::CreateSolidBackground( |
| 315 SkColorSetRGB(0xdb, 0x44, 0x37))); |
| 316 permanent_error_label_->SetBorder( |
| 317 views::Border::CreateEmptyBorder(10, kEdgePadding, 10, kEdgePadding)); |
| 318 permanent_error_label_->SetEnabledColor(SK_ColorWHITE); |
| 319 permanent_error_label_->SetAutoColorReadabilityEnabled(false); |
| 320 permanent_error_label_->SetVisible(false); |
| 321 permanent_error_label_->SetMultiLine(true); |
| 322 permanent_error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 323 main_contents_->AddChildView(permanent_error_label_); |
| 324 |
| 325 views::View* controls_container = new views::View(); |
| 326 controls_container->SetLayoutManager( |
| 327 new views::BoxLayout(views::BoxLayout::kVertical, kEdgePadding, 0, 0)); |
| 328 main_contents_->AddChildView(controls_container); |
| 329 |
| 330 views::Label* instructions = |
| 331 new views::Label(controller_->GetInstructionsMessage()); |
| 332 |
| 333 instructions->SetMultiLine(true); |
| 334 instructions->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 335 instructions->SetBorder(views::Border::CreateEmptyBorder(0, 0, 15, 0)); |
| 336 controls_container->AddChildView(instructions); |
| 337 |
| 338 views::View* input_row = new views::View(); |
| 339 input_row->SetLayoutManager( |
| 340 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5)); |
| 341 controls_container->AddChildView(input_row); |
| 342 |
| 343 if (controller_->ShouldRequestExpirationDate()) { |
| 344 month_input_ = new views::Combobox(&month_combobox_model_); |
| 345 month_input_->set_listener(this); |
| 346 input_row->AddChildView(month_input_); |
| 347 input_row->AddChildView(new views::Label(l10n_util::GetStringUTF16( |
| 348 IDS_AUTOFILL_CARD_UNMASK_EXPIRATION_DATE_SEPARATOR))); |
| 349 year_input_ = new views::Combobox(&year_combobox_model_); |
| 350 year_input_->set_listener(this); |
| 351 input_row->AddChildView(year_input_); |
| 352 input_row->AddChildView(new views::Label(base::ASCIIToUTF16(" "))); |
| 353 } |
| 354 |
| 355 cvc_input_ = new DecoratedTextfield( |
| 356 base::string16(), |
| 357 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC), this); |
| 358 cvc_input_->set_default_width_in_chars(8); |
| 359 input_row->AddChildView(cvc_input_); |
| 360 |
| 361 views::ImageView* cvc_image = new views::ImageView(); |
| 362 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| 363 cvc_image->SetImage(rb.GetImageSkiaNamed(controller_->GetCvcImageRid())); |
| 364 input_row->AddChildView(cvc_image); |
| 365 |
| 366 // Reserve vertical space for the error label, assuming it's one line. |
| 367 error_label_ = new views::Label(base::ASCIIToUTF16(" ")); |
| 368 error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 369 error_label_->SetEnabledColor(kWarningColor); |
| 370 error_label_->SetBorder(views::Border::CreateEmptyBorder(3, 0, 5, 0)); |
| 371 controls_container->AddChildView(error_label_); |
| 372 |
| 373 progress_overlay_ = new views::View(); |
| 374 views::BoxLayout* progress_layout = |
| 375 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); |
| 376 progress_layout->set_main_axis_alignment( |
| 377 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER); |
| 378 progress_layout->set_cross_axis_alignment( |
| 379 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); |
| 380 progress_overlay_->SetLayoutManager(progress_layout); |
| 381 |
| 382 progress_overlay_->SetVisible(false); |
| 383 AddChildView(progress_overlay_); |
| 384 |
| 385 progress_label_ = new views::Label(l10n_util::GetStringUTF16( |
| 386 IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_IN_PROGRESS)); |
| 387 progress_overlay_->AddChildView(progress_label_); |
| 388 } |
| 389 |
| 390 bool CardUnmaskPromptViews::ExpirationDateIsValid() const { |
| 391 if (!controller_->ShouldRequestExpirationDate()) |
| 392 return true; |
| 393 |
| 394 return controller_->InputExpirationIsValid( |
| 395 month_input_->GetTextForRow(month_input_->selected_index()), |
| 396 year_input_->GetTextForRow(year_input_->selected_index())); |
| 397 } |
| 398 |
| 399 void CardUnmaskPromptViews::ClosePrompt() { |
| 400 GetWidget()->Close(); |
| 401 } |
| 402 |
| 438 } // namespace autofill | 403 } // namespace autofill |
| OLD | NEW |