blob: 80043e1f9d6440c5a21280f25fe8bead9d8372c5 [file] [log] [blame]
// Copyright 2016 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/payments/payment_sheet_view_controller.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/views/payments/payment_request_dialog_view.h"
#include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h"
#include "chrome/browser/ui/views/payments/payment_request_row_view.h"
#include "chrome/browser/ui/views/payments/payment_request_views_util.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/payments/core/currency_formatter.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/range/range.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view.h"
namespace payments {
namespace {
constexpr int kFirstTagValue = static_cast<int>(
payments::PaymentRequestCommonTags::PAYMENT_REQUEST_COMMON_TAG_MAX);
enum class PaymentSheetViewControllerTags {
// The tag for the button that navigates to the Order Summary sheet.
SHOW_ORDER_SUMMARY_BUTTON = kFirstTagValue,
SHOW_SHIPPING_BUTTON,
SHOW_PAYMENT_METHOD_BUTTON,
SHOW_CONTACT_INFO_BUTTON,
PAY_BUTTON
};
int ComputeWidestNameColumnViewWidth() {
// The name colums in each row should all have the same width, large enough to
// accomodate the longest piece of text they contain. Because of this, each
// row's GridLayout requires its first column to have a fixed width of the
// correct size. To measure the required size, layout a label with each
// section name, measure its width, then initialize |widest_column_width|
// with the largest value.
std::vector<int> section_names{
IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SECTION_NAME,
IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME,
IDS_PAYMENT_REQUEST_SHIPPING_SECTION_NAME};
int widest_column_width = 0;
views::Label label(base::ASCIIToUTF16(""));
for (int name_id : section_names) {
label.SetText(l10n_util::GetStringUTF16(name_id));
widest_column_width = std::max(
label.GetPreferredSize().width(),
widest_column_width);
}
return widest_column_width;
}
// Creates a clickable row to be displayed in the Payment Sheet. It contains
// a section name and some content, followed by a chevron as a clickability
// affordance. Both, either, or none of |content_view| and |extra_content_view|
// may be present, the difference between the two being that content is pinned
// to the left and extra_content is pinned to the right.
// The row also displays a light gray horizontal ruler on its lower boundary.
// The name column has a fixed width equal to |name_column_width|.
// +----------------------------+
// | Name | Content | Extra | > |
// +~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- ruler
std::unique_ptr<views::Button> CreatePaymentSheetRow(
views::ButtonListener* listener,
const base::string16& section_name,
std::unique_ptr<views::View> content_view,
std::unique_ptr<views::View> extra_content_view,
int name_column_width) {
std::unique_ptr<PaymentRequestRowView> row =
base::MakeUnique<PaymentRequestRowView>(listener);
views::GridLayout* layout = new views::GridLayout(row.get());
// The rows have extra inset compared to the header so that their right edge
// lines up with the close button's X rather than its invisible right edge.
layout->SetInsets(
kPaymentRequestRowVerticalInsets, kPaymentRequestRowHorizontalInsets,
kPaymentRequestRowVerticalInsets,
kPaymentRequestRowHorizontalInsets + kPaymentRequestRowExtraRightInset);
row->SetLayoutManager(layout);
views::ColumnSet* columns = layout->AddColumnSet(0);
// A column for the section name.
columns->AddColumn(views::GridLayout::LEADING,
views::GridLayout::LEADING,
0,
views::GridLayout::FIXED,
name_column_width,
0);
constexpr int kPaddingColumnsWidth = 25;
columns->AddPaddingColumn(0, kPaddingColumnsWidth);
// A column for the content.
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::LEADING,
1, views::GridLayout::USE_PREF, 0, 0);
// A column for the extra content.
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
0, views::GridLayout::USE_PREF, 0, 0);
columns->AddPaddingColumn(0, kPaddingColumnsWidth);
// A column for the chevron.
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
0, views::GridLayout::USE_PREF, 0, 0);
layout->StartRow(0, 0);
views::Label* name_label = new views::Label(section_name);
layout->AddView(name_label);
if (content_view) {
content_view->set_can_process_events_within_subtree(false);
layout->AddView(content_view.release());
} else {
layout->SkipColumns(1);
}
if (extra_content_view) {
extra_content_view->set_can_process_events_within_subtree(false);
layout->AddView(extra_content_view.release());
} else {
layout->SkipColumns(1);
}
views::ImageView* chevron = new views::ImageView();
chevron->set_can_process_events_within_subtree(false);
chevron->SetImage(gfx::CreateVectorIcon(
views::kSubmenuArrowIcon,
color_utils::DeriveDefaultIconColor(name_label->enabled_color())));
layout->AddView(chevron);
return std::move(row);
}
// Creates a GridLayout object to be used in the Order Summary section's list of
// items and the list of prices. |host| is the view that will be assigned the
// returned Layout Manager and |trailing| indicates whether the elements added
// to the manager should have trailing horizontal alignment. If trailing is
// |false|, their horizontal alignment is leading.
std::unique_ptr<views::GridLayout> CreateOrderSummarySectionContainerLayout(
views::View* host,
bool trailing) {
std::unique_ptr<views::GridLayout> layout =
base::MakeUnique<views::GridLayout>(host);
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddColumn(
trailing ? views::GridLayout::TRAILING : views::GridLayout::LEADING,
views::GridLayout::LEADING, 1, views::GridLayout::USE_PREF, 0, 0);
return layout;
}
} // namespace
PaymentSheetViewController::PaymentSheetViewController(
PaymentRequest* request,
PaymentRequestDialogView* dialog)
: PaymentRequestSheetController(request, dialog),
pay_button_(nullptr),
widest_name_column_view_width_(ComputeWidestNameColumnViewWidth()) {
request->AddObserver(this);
}
PaymentSheetViewController::~PaymentSheetViewController() {
request()->RemoveObserver(this);
}
std::unique_ptr<views::View> PaymentSheetViewController::CreateView() {
std::unique_ptr<views::View> content_view = base::MakeUnique<views::View>();
views::GridLayout* layout = new views::GridLayout(content_view.get());
content_view->SetLayoutManager(layout);
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
1, views::GridLayout::USE_PREF, 0, 0);
// The shipping address and contact info rows are optional.
layout->StartRow(0, 0);
layout->AddView(CreatePaymentSheetSummaryRow().release());
if (request()->request_shipping()) {
layout->StartRow(0, 0);
layout->AddView(CreateShippingRow().release());
}
layout->StartRow(0, 0);
layout->AddView(CreatePaymentMethodRow().release());
if (request()->request_payer_name() || request()->request_payer_email() ||
request()->request_payer_phone()) {
layout->StartRow(0, 0);
layout->AddView(CreateContactInfoRow().release());
}
return CreatePaymentView(
CreateSheetHeaderView(
false,
l10n_util::GetStringUTF16(IDS_PAYMENT_REQUEST_PAYMENT_SHEET_TITLE),
this),
std::move(content_view));
}
void PaymentSheetViewController::OnSelectedInformationChanged() {
UpdatePayButtonState(request()->is_ready_to_pay());
}
std::unique_ptr<views::Button>
PaymentSheetViewController::CreatePrimaryButton() {
std::unique_ptr<views::Button> button(
views::MdTextButton::CreateSecondaryUiBlueButton(
this, l10n_util::GetStringUTF16(IDS_PAYMENTS_PAY_BUTTON)));
button->set_tag(static_cast<int>(PaymentRequestCommonTags::PAY_BUTTON_TAG));
button->set_id(static_cast<int>(DialogViewID::PAY_BUTTON));
pay_button_ = button.get();
UpdatePayButtonState(request()->is_ready_to_pay());
return button;
}
// Adds the product logo to the footer.
// +---------------------------------------------------------+
// | (•) chrome | PAY | CANCEL |
// +---------------------------------------------------------+
std::unique_ptr<views::View>
PaymentSheetViewController::CreateExtraFooterView() {
std::unique_ptr<views::View> content_view = base::MakeUnique<views::View>();
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
layout->set_cross_axis_alignment(
views::BoxLayout::CROSS_AXIS_ALIGNMENT_START);
content_view->SetLayoutManager(layout);
// Adds the Chrome logo image.
std::unique_ptr<views::ImageView> chrome_logo =
base::MakeUnique<views::ImageView>();
chrome_logo->set_can_process_events_within_subtree(false);
chrome_logo->SetImage(ResourceBundle::GetSharedInstance()
.GetImageNamed(IDR_PRODUCT_LOGO_NAME_22)
.AsImageSkia());
chrome_logo->SetTooltipText(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
content_view->AddChildView(chrome_logo.release());
return content_view;
}
void PaymentSheetViewController::ButtonPressed(
views::Button* sender, const ui::Event& event) {
switch (sender->tag()) {
case static_cast<int>(
PaymentSheetViewControllerTags::SHOW_ORDER_SUMMARY_BUTTON):
dialog()->ShowOrderSummary();
break;
case static_cast<int>(PaymentSheetViewControllerTags::SHOW_SHIPPING_BUTTON):
dialog()->ShowShippingListSheet();
break;
case static_cast<int>(
PaymentSheetViewControllerTags::SHOW_PAYMENT_METHOD_BUTTON):
dialog()->ShowPaymentMethodSheet();
break;
case static_cast<int>(
PaymentSheetViewControllerTags::SHOW_CONTACT_INFO_BUTTON):
dialog()->ShowContactInfoSheet();
break;
default:
PaymentRequestSheetController::ButtonPressed(sender, event);
break;
}
}
void PaymentSheetViewController::UpdatePayButtonState(bool enabled) {
pay_button_->SetEnabled(enabled);
}
// Creates the Order Summary row, which contains an "Order Summary" label,
// an inline list of display items, a Total Amount label, and a Chevron.
// +----------------------------------------------+
// | Order Summary Item 1 $ 1.34 |
// | Item 2 $ 2.00 > |
// | 2 more items... |
// | Total USD $12.34 |
// +----------------------------------------------+
std::unique_ptr<views::Button>
PaymentSheetViewController::CreatePaymentSheetSummaryRow() {
std::unique_ptr<views::View> item_summaries = base::MakeUnique<views::View>();
std::unique_ptr<views::GridLayout> item_summaries_layout =
CreateOrderSummarySectionContainerLayout(item_summaries.get(),
/* trailing =*/false);
std::unique_ptr<views::View> item_amounts = base::MakeUnique<views::View>();
std::unique_ptr<views::GridLayout> item_amounts_layout =
CreateOrderSummarySectionContainerLayout(item_amounts.get(),
/* trailing =*/true);
const std::vector<mojom::PaymentItemPtr>& items =
request()->details()->display_items;
// The inline items section contains the first 2 display items of the
// request's details, followed by a label indicating "N more items..." if
// there are more than 2 items in the details. The total label and amount
// always follow.
constexpr int kMaxNumberOfItemsShown = 2;
for (size_t i = 0; i < items.size() && i < kMaxNumberOfItemsShown; ++i) {
item_summaries_layout->StartRow(0, 0);
item_summaries_layout->AddView(
new views::Label(base::ASCIIToUTF16(items[i]->label)));
item_amounts_layout->StartRow(0, 0);
item_amounts_layout->AddView(new views::Label(
request()->GetFormattedCurrencyAmount(items[i]->amount->value)));
}
int hidden_item_count = items.size() - kMaxNumberOfItemsShown;
if (hidden_item_count > 0) {
item_summaries_layout->StartRow(0, 0);
std::unique_ptr<views::Label> label = base::MakeUnique<views::Label>(
l10n_util::GetStringFUTF16(IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS,
base::IntToString16(hidden_item_count)));
label->SetDisabledColor(label->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelDisabledColor));
label->SetEnabled(false);
item_summaries_layout->AddView(label.release());
item_amounts_layout->StartRow(0, 0);
item_amounts_layout->AddView(new views::Label(base::ASCIIToUTF16("")));
}
item_summaries_layout->StartRow(0, 0);
item_summaries_layout->AddView(
CreateBoldLabel(base::ASCIIToUTF16(request()->details()->total->label))
.release());
item_amounts_layout->StartRow(0, 0);
item_amounts_layout->AddView(
CreateBoldLabel(
l10n_util::GetStringFUTF16(
IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT,
base::UTF8ToUTF16(request()->GetFormattedCurrencyCode()),
request()->GetFormattedCurrencyAmount(
request()->details()->total->amount->value)))
.release());
item_summaries->SetLayoutManager(item_summaries_layout.release());
item_amounts->SetLayoutManager(item_amounts_layout.release());
std::unique_ptr<views::Button> section = CreatePaymentSheetRow(
this,
l10n_util::GetStringUTF16(IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SECTION_NAME),
std::move(item_summaries), std::move(item_amounts),
widest_name_column_view_width_);
section->set_tag(static_cast<int>(
PaymentSheetViewControllerTags::SHOW_ORDER_SUMMARY_BUTTON));
section->set_id(
static_cast<int>(DialogViewID::PAYMENT_SHEET_SUMMARY_SECTION));
return section;
}
std::unique_ptr<views::View>
PaymentSheetViewController::CreateShippingSectionContent() {
auto* profile = request()->selected_shipping_profile();
// TODO(tmartino): Empty string param is app locale; this should be passed
// at construct-time and stored as a member in a future CL.
return profile ? payments::GetShippingAddressLabel(AddressStyleType::SUMMARY,
std::string(), *profile)
: base::MakeUnique<views::Label>(base::string16());
}
// Creates the Shipping row, which contains a "Shipping address" label, the
// user's selected shipping address, and a chevron.
// +----------------------------------------------+
// | Shipping Address Barack Obama |
// | 1600 Pennsylvania Ave. > |
// | 1800MYPOTUS |
// +----------------------------------------------+
std::unique_ptr<views::Button> PaymentSheetViewController::CreateShippingRow() {
std::unique_ptr<views::Button> section = CreatePaymentSheetRow(
this,
l10n_util::GetStringUTF16(IDS_PAYMENT_REQUEST_SHIPPING_SECTION_NAME),
CreateShippingSectionContent(), std::unique_ptr<views::View>(nullptr),
widest_name_column_view_width_);
section->set_tag(
static_cast<int>(PaymentSheetViewControllerTags::SHOW_SHIPPING_BUTTON));
section->set_id(
static_cast<int>(DialogViewID::PAYMENT_SHEET_SHIPPING_SECTION));
return section;
}
// Creates the Payment Method row, which contains a "Payment" label, the user's
// masked Credit Card details, the icon for the selected card, and a chevron.
// +----------------------------------------------+
// | Payment Visa ****0000 |
// | John Smith | VISA | > |
// | |
// +----------------------------------------------+
std::unique_ptr<views::Button>
PaymentSheetViewController::CreatePaymentMethodRow() {
autofill::CreditCard* selected_card = request()->selected_credit_card();
std::unique_ptr<views::View> content_view;
std::unique_ptr<views::ImageView> card_icon_view;
if (selected_card) {
content_view = base::MakeUnique<views::View>();
views::GridLayout* layout = new views::GridLayout(content_view.get());
content_view->SetLayoutManager(layout);
views::ColumnSet* columns = layout->AddColumnSet(0);
columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
1, views::GridLayout::USE_PREF, 0, 0);
layout->StartRow(0, 0);
layout->AddView(new views::Label(selected_card->TypeAndLastFourDigits()));
layout->StartRow(0, 0);
layout->AddView(new views::Label(
selected_card->GetInfo(
autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL),
g_browser_process->GetApplicationLocale())));
card_icon_view = CreateCardIconView(selected_card->type());
card_icon_view->SetImageSize(gfx::Size(32, 20));
}
std::unique_ptr<views::Button> section = CreatePaymentSheetRow(
this,
l10n_util::GetStringUTF16(
IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME),
std::move(content_view),
std::move(card_icon_view),
widest_name_column_view_width_);
section->set_tag(static_cast<int>(
PaymentSheetViewControllerTags::SHOW_PAYMENT_METHOD_BUTTON));
section->set_id(
static_cast<int>(DialogViewID::PAYMENT_SHEET_PAYMENT_METHOD_SECTION));
return section;
}
std::unique_ptr<views::View>
PaymentSheetViewController::CreateContactInfoSectionContent() {
auto* profile = request()->selected_contact_profile();
// TODO(tmartino): Replace empty string with app locale.
return profile ? payments::GetContactInfoLabel(AddressStyleType::SUMMARY,
std::string(), *profile, true,
true, true)
: base::MakeUnique<views::Label>(base::string16());
}
// Creates the Contact Info row, which contains a "Contact info" label; the
// name, email address, and/or phone number; and a chevron.
// +----------------------------------------------+
// | Contact info Barack Obama |
// | 1800MYPOTUS > |
// | potus@whitehouse.gov |
// +----------------------------------------------+
std::unique_ptr<views::Button>
PaymentSheetViewController::CreateContactInfoRow() {
std::unique_ptr<views::Button> section = CreatePaymentSheetRow(
this,
l10n_util::GetStringUTF16(IDS_PAYMENT_REQUEST_CONTACT_INFO_SECTION_NAME),
CreateContactInfoSectionContent(), std::unique_ptr<views::View>(nullptr),
widest_name_column_view_width_);
section->set_tag(static_cast<int>(
PaymentSheetViewControllerTags::SHOW_CONTACT_INFO_BUTTON));
section->set_id(
static_cast<int>(DialogViewID::PAYMENT_SHEET_CONTACT_INFO_SECTION));
return section;
}
} // namespace payments