blob: 557d3f2a4b72f8341ca0b238a855fc3a5864e450 [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_request_views_util.h"
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h"
#include "chrome/browser/ui/views/payments/payment_request_sheet_controller.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/theme_resources.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/geo/phone_number_i18n.h"
#include "components/payments/content/icon/icon_size.h"
#include "components/payments/core/payment_options_provider.h"
#include "components/payments/core/payment_request_data_util.h"
#include "components/payments/core/payments_profile_comparator.h"
#include "components/payments/core/strings_util.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/default_style.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.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/layout/layout_provider.h"
#include "ui/views/painter.h"
#include "ui/views/view.h"
namespace payments {
namespace {
// |s1|, |s2|, and |s3| are lines identifying the profile. |s1| is the
// "headline" which may be emphasized depending on |type|. If |enabled| is
// false, the labels will look disabled.
std::unique_ptr<views::View> GetBaseProfileLabel(
AddressStyleType type,
const base::string16& s1,
const base::string16& s2,
const base::string16& s3,
base::string16* accessible_content,
bool enabled = true) {
DCHECK(accessible_content);
std::unique_ptr<views::View> container = std::make_unique<views::View>();
std::unique_ptr<views::BoxLayout> layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
container->SetLayoutManager(std::move(layout));
if (!s1.empty()) {
const int text_style = type == AddressStyleType::DETAILED
? static_cast<int>(STYLE_EMPHASIZED)
: static_cast<int>(views::style::STYLE_PRIMARY);
std::unique_ptr<views::Label> label = std::make_unique<views::Label>(
s1, views::style::CONTEXT_LABEL, text_style);
label->SetID(static_cast<int>(DialogViewID::PROFILE_LABEL_LINE_1));
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
if (!enabled) {
label->SetEnabledColor(label->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelDisabledColor));
}
container->AddChildView(std::move(label));
}
if (!s2.empty()) {
std::unique_ptr<views::Label> label = std::make_unique<views::Label>(s2);
label->SetID(static_cast<int>(DialogViewID::PROFILE_LABEL_LINE_2));
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
if (!enabled) {
label->SetEnabledColor(label->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelDisabledColor));
}
container->AddChildView(std::move(label));
}
if (!s3.empty()) {
std::unique_ptr<views::Label> label = std::make_unique<views::Label>(s3);
label->SetID(static_cast<int>(DialogViewID::PROFILE_LABEL_LINE_3));
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
if (!enabled) {
label->SetEnabledColor(label->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelDisabledColor));
}
container->AddChildView(std::move(label));
}
*accessible_content = l10n_util::GetStringFUTF16(
IDS_PAYMENTS_PROFILE_LABELS_ACCESSIBLE_FORMAT, s1, s2, s3);
return container;
}
// Returns a label representing the |profile| as a shipping address. See
// GetBaseProfileLabel() for more documentation.
std::unique_ptr<views::View> GetShippingAddressLabel(
AddressStyleType type,
const std::string& locale,
const autofill::AutofillProfile& profile,
base::string16* accessible_content,
bool enabled) {
DCHECK(accessible_content);
base::string16 name = profile.GetInfo(autofill::NAME_FULL, locale);
base::string16 address =
GetShippingAddressLabelFormAutofillProfile(profile, locale);
base::string16 phone =
autofill::i18n::GetFormattedPhoneNumberForDisplay(profile, locale);
return GetBaseProfileLabel(type, name, address, phone, accessible_content,
enabled);
}
std::unique_ptr<views::Label> GetLabelForMissingInformation(
const base::string16& missing_info) {
std::unique_ptr<views::Label> label =
std::make_unique<views::Label>(missing_info, CONTEXT_BODY_TEXT_SMALL);
label->SetID(static_cast<int>(DialogViewID::PROFILE_LABEL_ERROR));
// Missing information typically has a nice shade of blue.
label->SetEnabledColor(label->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LinkEnabled));
return label;
}
// Paints the gray horizontal line that doesn't span the entire width of the
// dialog at the bottom of the view it borders.
class PaymentRequestRowBorderPainter : public views::Painter {
public:
explicit PaymentRequestRowBorderPainter(SkColor color) : color_(color) {}
~PaymentRequestRowBorderPainter() override {}
// views::Painter:
gfx::Size GetMinimumSize() const override {
return gfx::Size(2 * payments::kPaymentRequestRowHorizontalInsets, 1);
}
void Paint(gfx::Canvas* canvas, const gfx::Size& size) override {
int line_height = size.height() - 1;
canvas->DrawLine(
gfx::PointF(payments::kPaymentRequestRowHorizontalInsets, line_height),
gfx::PointF(size.width() - payments::kPaymentRequestRowHorizontalInsets,
line_height),
color_);
}
private:
SkColor color_;
DISALLOW_COPY_AND_ASSIGN(PaymentRequestRowBorderPainter);
};
} // namespace
int GetActualDialogWidth() {
static int actual_width =
views::LayoutProvider::Get()->GetSnappedDialogWidth(kDialogMinWidth);
return actual_width;
}
void PopulateSheetHeaderView(bool show_back_arrow,
std::unique_ptr<views::View> header_content_view,
views::ButtonListener* listener,
views::View* container,
std::unique_ptr<views::Background> background) {
SkColor background_color = background->get_color();
container->SetBackground(std::move(background));
views::GridLayout* layout =
container->SetLayoutManager(std::make_unique<views::GridLayout>());
constexpr int kVerticalInset = 14;
constexpr int kHeaderHorizontalInset = 16;
container->SetBorder(
views::CreateEmptyBorder(kVerticalInset, kHeaderHorizontalInset,
kVerticalInset, kHeaderHorizontalInset));
views::ColumnSet* columns = layout->AddColumnSet(0);
// A column for the optional back arrow.
columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
views::GridLayout::kFixedSize, views::GridLayout::USE_PREF,
0, 0);
constexpr int kPaddingBetweenArrowAndTitle = 8;
if (show_back_arrow)
columns->AddPaddingColumn(views::GridLayout::kFixedSize,
kPaddingBetweenArrowAndTitle);
// A column for the title.
columns->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 1.0,
views::GridLayout::USE_PREF, 0, 0);
layout->StartRow(views::GridLayout::kFixedSize, 0);
if (!show_back_arrow) {
layout->SkipColumns(1);
} else {
auto back_arrow = views::CreateVectorImageButton(listener);
views::SetImageFromVectorIcon(
back_arrow.get(), vector_icons::kBackArrowIcon,
color_utils::GetColorWithMaxContrast(background_color));
constexpr int kBackArrowSize = 16;
back_arrow->SetSize(gfx::Size(kBackArrowSize, kBackArrowSize));
back_arrow->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
back_arrow->set_tag(
static_cast<int>(PaymentRequestCommonTags::BACK_BUTTON_TAG));
back_arrow->SetID(static_cast<int>(DialogViewID::BACK_BUTTON));
back_arrow->SetAccessibleName(l10n_util::GetStringUTF16(IDS_PAYMENTS_BACK));
layout->AddView(std::move(back_arrow));
}
layout->AddView(std::move(header_content_view));
}
std::unique_ptr<views::ImageView> CreateInstrumentIconView(
int icon_resource_id,
gfx::ImageSkia img,
const base::string16& tooltip_text,
float opacity) {
std::unique_ptr<views::ImageView> icon_view =
std::make_unique<views::ImageView>();
icon_view->set_can_process_events_within_subtree(false);
if (!img.isNull() || !icon_resource_id) {
icon_view->SetImage(img);
float width = base::checked_cast<float>(img.width());
float height = base::checked_cast<float>(img.height());
float ratio = 1;
if (width && height)
ratio = width / height;
// We should set image size in density indepent pixels here, since
// views::ImageView objects are rastered at the device scale factor.
icon_view->SetImageSize(gfx::Size(
ratio * IconSizeCalculator::kPaymentAppDeviceIndependentIdealIconHeight,
IconSizeCalculator::kPaymentAppDeviceIndependentIdealIconHeight));
} else {
icon_view->SetImage(ui::ResourceBundle::GetSharedInstance()
.GetImageNamed(icon_resource_id)
.AsImageSkia());
// Images from |icon_resource_id| are 32x20 credit cards.
icon_view->SetImageSize(gfx::Size(32, 20));
}
icon_view->set_tooltip_text(tooltip_text);
icon_view->SetPaintToLayer();
icon_view->layer()->SetFillsBoundsOpaquely(false);
icon_view->layer()->SetOpacity(opacity);
return icon_view;
}
std::unique_ptr<views::View> CreateProductLogoFooterView() {
std::unique_ptr<views::View> content_view = std::make_unique<views::View>();
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0);
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
content_view->SetLayoutManager(std::move(layout));
// Adds the Chrome logo image.
std::unique_ptr<views::ImageView> chrome_logo =
std::make_unique<views::ImageView>();
chrome_logo->set_can_process_events_within_subtree(false);
chrome_logo->SetImage(ui::ResourceBundle::GetSharedInstance()
.GetImageNamed(IDR_PRODUCT_LOGO_NAME_22)
.AsImageSkia());
chrome_logo->set_tooltip_text(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
content_view->AddChildView(std::move(chrome_logo));
return content_view;
}
std::unique_ptr<views::View> GetShippingAddressLabelWithMissingInfo(
AddressStyleType type,
const std::string& locale,
const autofill::AutofillProfile& profile,
const PaymentsProfileComparator& comp,
base::string16* accessible_content,
bool enabled) {
DCHECK(accessible_content);
std::unique_ptr<views::View> base_label = GetShippingAddressLabel(
type, locale, profile, accessible_content, enabled);
base::string16 missing = comp.GetStringForMissingShippingFields(profile);
if (!missing.empty()) {
base_label->AddChildView(GetLabelForMissingInformation(missing));
*accessible_content = l10n_util::GetStringFUTF16(
IDS_PAYMENTS_ACCESSIBLE_LABEL_WITH_ERROR, *accessible_content, missing);
}
return base_label;
}
// TODO(anthonyvd): unit test the label layout.
std::unique_ptr<views::View> GetContactInfoLabel(
AddressStyleType type,
const std::string& locale,
const autofill::AutofillProfile& profile,
const PaymentOptionsProvider& options,
const PaymentsProfileComparator& comp,
base::string16* accessible_content) {
DCHECK(accessible_content);
base::string16 name = options.request_payer_name()
? profile.GetInfo(autofill::NAME_FULL, locale)
: base::string16();
base::string16 phone =
options.request_payer_phone()
? autofill::i18n::GetFormattedPhoneNumberForDisplay(profile, locale)
: base::string16();
base::string16 email = options.request_payer_email()
? profile.GetInfo(autofill::EMAIL_ADDRESS, locale)
: base::string16();
std::unique_ptr<views::View> base_label =
GetBaseProfileLabel(type, name, phone, email, accessible_content);
base::string16 missing = comp.GetStringForMissingContactFields(profile);
if (!missing.empty()) {
base_label->AddChildView(GetLabelForMissingInformation(missing));
*accessible_content = l10n_util::GetStringFUTF16(
IDS_PAYMENTS_ACCESSIBLE_LABEL_WITH_ERROR, *accessible_content, missing);
}
return base_label;
}
std::unique_ptr<views::Border> CreatePaymentRequestRowBorder(
SkColor color,
const gfx::Insets& insets) {
return views::CreateBorderPainter(
std::make_unique<PaymentRequestRowBorderPainter>(color), insets);
}
std::unique_ptr<views::Label> CreateBoldLabel(const base::string16& text) {
return std::make_unique<views::Label>(text, views::style::CONTEXT_LABEL,
STYLE_EMPHASIZED);
}
std::unique_ptr<views::Label> CreateMediumLabel(const base::string16& text) {
// TODO(tapted): This should refer to a style in the Chrome typography spec.
// Also, it needs to handle user setups where the default font is BOLD already
// since asking for a MEDIUM font will give a lighter font.
std::unique_ptr<views::Label> label = std::make_unique<views::Label>(text);
label->SetFontList(
ui::ResourceBundle::GetSharedInstance().GetFontListWithDelta(
ui::kLabelFontSizeDelta, gfx::Font::NORMAL,
gfx::Font::Weight::MEDIUM));
return label;
}
std::unique_ptr<views::Label> CreateHintLabel(
const base::string16& text,
gfx::HorizontalAlignment alignment) {
std::unique_ptr<views::Label> label = std::make_unique<views::Label>(
text, views::style::CONTEXT_LABEL, STYLE_HINT);
label->SetHorizontalAlignment(alignment);
return label;
}
std::unique_ptr<views::View> CreateShippingOptionLabel(
payments::mojom::PaymentShippingOption* shipping_option,
const base::string16& formatted_amount,
bool emphasize_label,
base::string16* accessible_content) {
DCHECK(accessible_content);
std::unique_ptr<views::View> container = std::make_unique<views::View>();
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStart);
container->SetLayoutManager(std::move(layout));
if (shipping_option) {
const base::string16& text = base::UTF8ToUTF16(shipping_option->label);
std::unique_ptr<views::Label> shipping_label =
emphasize_label ? CreateMediumLabel(text)
: std::make_unique<views::Label>(text);
// Strings from the website may not match the locale of the device, so align
// them according to the language of the text. This will result, for
// example, in "he" labels being right-aligned in a browser that's using
// "en" locale.
shipping_label->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
shipping_label->SetID(
static_cast<int>(DialogViewID::SHIPPING_OPTION_DESCRIPTION));
container->AddChildView(std::move(shipping_label));
std::unique_ptr<views::Label> amount_label =
std::make_unique<views::Label>(formatted_amount);
amount_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
amount_label->SetID(static_cast<int>(DialogViewID::SHIPPING_OPTION_AMOUNT));
container->AddChildView(std::move(amount_label));
*accessible_content = l10n_util::GetStringFUTF16(
IDS_PAYMENTS_PROFILE_LABELS_ACCESSIBLE_FORMAT, text, formatted_amount,
base::string16());
}
return container;
}
std::unique_ptr<views::View> CreateWarningView(const base::string16& message,
bool show_icon) {
auto header_view = std::make_unique<views::View>();
// 8 pixels between the warning icon view (if present) and the text.
constexpr int kRowHorizontalSpacing = 8;
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(0, kPaymentRequestRowHorizontalInsets),
kRowHorizontalSpacing);
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
header_view->SetLayoutManager(std::move(layout));
auto label = std::make_unique<views::Label>(message);
// If the warning message comes from the websites, then align label
// according to the language of the website's text.
label->SetHorizontalAlignment(message.empty() ? gfx::ALIGN_LEFT
: gfx::ALIGN_TO_HEAD);
label->SetID(static_cast<int>(DialogViewID::WARNING_LABEL));
label->SetMultiLine(true);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
if (show_icon) {
auto warning_icon = std::make_unique<views::ImageView>();
warning_icon->set_can_process_events_within_subtree(false);
warning_icon->SetImage(gfx::CreateVectorIcon(
vector_icons::kWarningIcon, 16,
warning_icon->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_AlertSeverityHigh)));
header_view->AddChildView(std::move(warning_icon));
label->SetEnabledColor(label->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_AlertSeverityHigh));
}
header_view->AddChildView(std::move(label));
return header_view;
}
} // namespace payments