blob: c5c762717cd509e557521b8a215a4a1fa8cebd2c [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/autofill/save_card_offer_bubble_views.h"
#include <memory>
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/views/autofill/dialog_view_ids.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/grit/generated_resources.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_metrics.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/payments/legal_message_line.h"
#include "components/autofill/core/browser/ui/payments/save_card_bubble_controller.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/bubble/tooltip_icon.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/style/typography.h"
#include "ui/views/window/dialog_client_view.h"
namespace autofill {
namespace {
const int kTooltipBubbleWidth = 320;
const int kTooltipIconSize = 12;
} // namespace
SaveCardOfferBubbleViews::SaveCardOfferBubbleViews(
views::View* anchor_view,
const gfx::Point& anchor_point,
content::WebContents* web_contents,
SaveCardBubbleController* controller)
: SaveCardBubbleViews(anchor_view, anchor_point, web_contents, controller) {
}
views::View* SaveCardOfferBubbleViews::CreateExtraView() {
if (controller()->GetSyncState() !=
AutofillSyncSigninState::kSignedInAndWalletSyncTransportEnabled) {
return nullptr;
}
auto* upload_explanation_tooltip =
new views::TooltipIcon(l10n_util::GetStringUTF16(
IDS_AUTOFILL_SAVE_CARD_PROMPT_UPLOAD_EXPLANATION_TOOLTIP));
upload_explanation_tooltip->set_bubble_width(kTooltipBubbleWidth);
upload_explanation_tooltip->set_anchor_point_arrow(
views::BubbleBorder::Arrow::TOP_RIGHT);
return upload_explanation_tooltip;
}
views::View* SaveCardOfferBubbleViews::CreateFootnoteView() {
if (controller()->GetLegalMessageLines().empty())
return nullptr;
legal_message_view_ =
new LegalMessageView(controller()->GetLegalMessageLines(), this);
InitFootnoteView(legal_message_view_);
return legal_message_view_;
}
bool SaveCardOfferBubbleViews::Accept() {
if (controller()) {
controller()->OnSaveButton(
{cardholder_name_textfield_ ? cardholder_name_textfield_->text()
: base::string16(),
month_input_dropdown_ ? month_input_dropdown_->model()->GetItemAt(
month_input_dropdown_->selected_index())
: base::string16(),
year_input_dropdown_ ? year_input_dropdown_->model()->GetItemAt(
year_input_dropdown_->selected_index())
: base::string16()});
}
return true;
}
int SaveCardOfferBubbleViews::GetDialogButtons() const {
return base::FeatureList::IsEnabled(
features::kAutofillSaveCardImprovedUserConsent)
? ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL
: ui::DIALOG_BUTTON_OK;
}
base::string16 SaveCardOfferBubbleViews::GetDialogButtonLabel(
ui::DialogButton button) const {
return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK
? IDS_AUTOFILL_SAVE_CARD_PROMPT_ACCEPT
: IDS_NO_THANKS);
}
bool SaveCardOfferBubbleViews::IsDialogButtonEnabled(
ui::DialogButton button) const {
if (button == ui::DIALOG_BUTTON_CANCEL)
return true;
DCHECK_EQ(ui::DIALOG_BUTTON_OK, button);
if (cardholder_name_textfield_) {
// Make sure we are not requesting cardholder name and expiration date at
// the same time.
DCHECK(!month_input_dropdown_ && !year_input_dropdown_);
// If requesting the user confirm the name, it cannot be blank.
base::string16 trimmed_text;
base::TrimWhitespace(cardholder_name_textfield_->text(), base::TRIM_ALL,
&trimmed_text);
return !trimmed_text.empty();
}
// If requesting the user select the expiration date, it cannot be unselected
// or expired.
if (month_input_dropdown_ || year_input_dropdown_) {
// Make sure we are not requesting cardholder name and expiration date at
// the same time.
DCHECK(!cardholder_name_textfield_);
int month_value = 0, year_value = 0;
if (!base::StringToInt(month_input_dropdown_->model()->GetItemAt(
month_input_dropdown_->selected_index()),
&month_value) ||
!base::StringToInt(year_input_dropdown_->model()->GetItemAt(
year_input_dropdown_->selected_index()),
&year_value)) {
return false;
}
return IsValidCreditCardExpirationDate(year_value, month_value,
AutofillClock::Now());
}
return true;
}
void SaveCardOfferBubbleViews::StyledLabelLinkClicked(views::StyledLabel* label,
const gfx::Range& range,
int event_flags) {
if (!controller())
return;
controller()->OnLegalMessageLinkClicked(
legal_message_view_->GetUrlForLink(label, range));
}
void SaveCardOfferBubbleViews::ContentsChanged(
views::Textfield* sender,
const base::string16& new_contents) {
DCHECK_EQ(cardholder_name_textfield_, sender);
DialogModelChanged();
}
void SaveCardOfferBubbleViews::OnPerformAction(views::Combobox* sender) {
DCHECK(month_input_dropdown_ == sender || year_input_dropdown_ == sender);
DialogModelChanged();
}
SaveCardOfferBubbleViews::~SaveCardOfferBubbleViews() {}
std::unique_ptr<views::View> SaveCardOfferBubbleViews::CreateMainContentView() {
std::unique_ptr<views::View> view =
SaveCardBubbleViews::CreateMainContentView();
ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
view->set_id(controller()->IsUploadSave()
? DialogViewId::MAIN_CONTENT_VIEW_UPLOAD
: DialogViewId::MAIN_CONTENT_VIEW_LOCAL);
// If necessary, add the cardholder name label and textfield to the upload
// save dialog.
if (controller()->ShouldRequestNameFromUser()) {
std::unique_ptr<views::View> cardholder_name_label_row =
std::make_unique<views::View>();
// Set up cardholder name label.
// TODO(jsaul): DISTANCE_RELATED_BUTTON_HORIZONTAL isn't the right choice
// here, but DISTANCE_RELATED_CONTROL_HORIZONTAL gives too much
// padding. Make a new Harmony DistanceMetric?
cardholder_name_label_row->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(),
provider->GetDistanceMetric(
views::DISTANCE_RELATED_BUTTON_HORIZONTAL)));
std::unique_ptr<views::Label> cardholder_name_label =
std::make_unique<views::Label>(
l10n_util::GetStringUTF16(
IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME),
CONTEXT_BODY_TEXT_LARGE, ChromeTextStyle::STYLE_SECONDARY);
cardholder_name_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
cardholder_name_label_row->AddChildView(cardholder_name_label.release());
// Prepare the prefilled cardholder name.
base::string16 prefilled_name;
if (!features::
IsAutofillUpstreamBlankCardholderNameFieldExperimentEnabled()) {
prefilled_name =
base::UTF8ToUTF16(controller()->GetAccountInfo().full_name);
}
// Set up cardholder name label tooltip ONLY if the cardholder name
// textfield will be prefilled.
if (!prefilled_name.empty()) {
std::unique_ptr<views::TooltipIcon> cardholder_name_tooltip =
std::make_unique<views::TooltipIcon>(
l10n_util::GetStringUTF16(
IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME_TOOLTIP),
kTooltipIconSize);
cardholder_name_tooltip->set_anchor_point_arrow(
views::BubbleBorder::Arrow::TOP_LEFT);
cardholder_name_tooltip->set_id(DialogViewId::CARDHOLDER_NAME_TOOLTIP);
cardholder_name_label_row->AddChildView(
cardholder_name_tooltip.release());
}
// Set up cardholder name textfield.
DCHECK(!cardholder_name_textfield_);
cardholder_name_textfield_ = new views::Textfield();
cardholder_name_textfield_->set_controller(this);
cardholder_name_textfield_->set_id(DialogViewId::CARDHOLDER_NAME_TEXTFIELD);
cardholder_name_textfield_->SetAccessibleName(l10n_util::GetStringUTF16(
IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME));
cardholder_name_textfield_->SetTextInputType(
ui::TextInputType::TEXT_INPUT_TYPE_TEXT);
cardholder_name_textfield_->SetText(prefilled_name);
AutofillMetrics::LogSaveCardCardholderNamePrefilled(
!prefilled_name.empty());
// Add cardholder name elements to a single view, then to the final dialog.
std::unique_ptr<views::View> cardholder_name_view =
std::make_unique<views::View>();
cardholder_name_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical, gfx::Insets(),
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
cardholder_name_view->AddChildView(cardholder_name_label_row.release());
cardholder_name_view->AddChildView(cardholder_name_textfield_);
view->AddChildView(cardholder_name_view.release());
}
if (controller()->ShouldRequestExpirationDateFromUser())
view->AddChildView(CreateRequestExpirationDateView().release());
return view;
}
std::unique_ptr<views::View>
SaveCardOfferBubbleViews::CreateRequestExpirationDateView() {
auto expiration_date_view = std::make_unique<views::View>();
ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
expiration_date_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical, gfx::Insets(),
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
expiration_date_view->set_id(DialogViewId::EXPIRATION_DATE_VIEW);
// Set up the month and year comboboxes.
month_input_dropdown_ = new views::Combobox(&month_combobox_model_);
month_input_dropdown_->set_listener(this);
month_input_dropdown_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_EXPIRY_MONTH));
month_input_dropdown_->set_id(DialogViewId::EXPIRATION_DATE_DROPBOX_MONTH);
const CreditCard& card = controller()->GetCard();
// Pre-populate expiration date month if it is detected.
if (card.expiration_month()) {
month_combobox_model_.SetDefaultIndexByMonth(card.expiration_month());
month_input_dropdown_->SetSelectedIndex(
month_combobox_model_.GetDefaultIndex());
}
year_input_dropdown_ = new views::Combobox(&year_combobox_model_);
year_input_dropdown_->set_listener(this);
year_input_dropdown_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_EXPIRY_YEAR));
year_input_dropdown_->set_id(DialogViewId::EXPIRATION_DATE_DROPBOX_YEAR);
// Pre-populate expiration date year if it is not passed.
if (IsValidCreditCardExpirationYear(card.expiration_year(),
AutofillClock::Now())) {
year_combobox_model_.SetDefaultIndexByYear(card.expiration_year());
year_input_dropdown_->SetSelectedIndex(
year_combobox_model_.GetDefaultIndex());
}
auto input_row = std::make_unique<views::View>();
input_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(),
provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_HORIZONTAL_SMALL)));
input_row->AddChildView(month_input_dropdown_);
input_row->AddChildView(year_input_dropdown_);
// Set up expiration date label.
auto expiration_date_label = std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_SETTINGS_CREDIT_CARD_EXPIRATION_DATE),
CONTEXT_BODY_TEXT_LARGE, ChromeTextStyle::STYLE_SECONDARY);
expiration_date_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
expiration_date_view->AddChildView(expiration_date_label.release());
expiration_date_view->AddChildView(input_row.release());
return expiration_date_view;
}
} // namespace autofill