blob: f34cbcb90825c2f75e0eeb43d7891a0eb897ba71 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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/update_address_profile_view.h"
#include <algorithm>
#include "base/ranges/algorithm.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/views/autofill/autofill_bubble_utils.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/grit/theme_resources.h"
#include "components/autofill/core/browser/autofill_address_util.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/color/color_id.h"
#include "ui/gfx/paint_vector_icon.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/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/table_layout_view.h"
#include "ui/views/style/typography.h"
namespace autofill {
namespace {
constexpr int kIconSize = 16;
constexpr int kValuesLabelWidth = 190;
const gfx::VectorIcon& GetVectorIconForType(FieldType type) {
switch (type) {
case NAME_FULL:
return kAccountCircleIcon;
case ADDRESS_HOME_ADDRESS:
return vector_icons::kLocationOnIcon;
case EMAIL_ADDRESS:
return vector_icons::kEmailIcon;
case PHONE_HOME_WHOLE_NUMBER:
return vector_icons::kCallIcon;
default:
NOTREACHED_NORETURN();
}
}
// Creates a view that displays all values in `diff`. `are_new_values`
// decides which set of values from `diff` are displayed.
std::unique_ptr<views::View> CreateValuesView(
const std::vector<ProfileValueDifference>& diff,
bool are_new_values,
ui::ColorId icon_color) {
auto view = std::make_unique<views::View>();
view->SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kVertical)
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
.SetIgnoreDefaultMainAxisMargins(true)
.SetCollapseMargins(true)
.SetDefault(
views::kMarginsKey,
gfx::Insets::VH(ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_CONTROL_LIST_VERTICAL),
0));
for (const ProfileValueDifference& diff_entry : diff) {
const std::u16string& value =
are_new_values ? diff_entry.first_value : diff_entry.second_value;
// Don't add rows for empty original values.
if (value.empty())
continue;
views::View* value_row =
view->AddChildView(std::make_unique<views::View>());
value_row->SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetCrossAxisAlignment(views::LayoutAlignment::kStart)
.SetIgnoreDefaultMainAxisMargins(true)
.SetCollapseMargins(true)
.SetDefault(
views::kMarginsKey,
gfx::Insets::VH(0, ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_LABEL_HORIZONTAL)));
auto label_view =
std::make_unique<views::Label>(value, views::style::CONTEXT_LABEL);
label_view->SetMultiLine(true);
label_view->SizeToFit(kValuesLabelWidth);
label_view->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
auto icon_view = std::make_unique<views::ImageView>();
icon_view->SetImage(ui::ImageModel::FromVectorIcon(
GetVectorIconForType(diff_entry.type), icon_color, kIconSize));
// The container aligns the icon vertically in the middle of the first label
// line, the icon size is expected to be smaller than the label height.
auto icon_container =
views::Builder<views::BoxLayoutView>()
.SetPreferredSize(gfx::Size(kIconSize, label_view->GetLineHeight()))
.SetCrossAxisAlignment(
views::BoxLayout::CrossAxisAlignment::kCenter)
.Build();
icon_container->AddChildView(std::move(icon_view));
value_row->AddChildView(std::move(icon_container));
value_row->AddChildView(std::move(label_view));
}
return view;
}
// Add a row in `layout` that contains a label and a view displays all the
// values in `values`. Labels are added only if `show_row_label` is true.
void AddValuesRow(views::TableLayoutView* layout_view,
const std::vector<ProfileValueDifference>& diff,
bool show_row_label,
views::Button::PressedCallback edit_button_callback) {
bool are_new_values = !!edit_button_callback;
layout_view->AddRows(1, /*vertical_resize=*/views::TableLayout::kFixedSize);
if (show_row_label) {
auto label = std::make_unique<views::Label>(
l10n_util::GetStringUTF16(
are_new_values
? IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_NEW_VALUES_SECTION_LABEL
: IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_OLD_VALUES_SECTION_LABEL),
views::style::CONTEXT_LABEL, views::style::STYLE_SECONDARY);
layout_view->AddChildView(std::move(label));
}
ui::ColorId icon_color = are_new_values ? ui::kColorButtonBackgroundProminent
: ui::kColorIconSecondary;
layout_view->AddChildView(CreateValuesView(diff, are_new_values, icon_color));
if (are_new_values) {
std::unique_ptr<views::ImageButton> edit_button =
CreateEditButton(std::move(edit_button_callback));
edit_button->SetProperty(views::kElementIdentifierKey,
UpdateAddressProfileView::kEditButtonViewId);
layout_view->AddChildView(std::move(edit_button));
}
}
// Returns true if there is there is at least one entry in `diff` with
// non-empty second value.
bool HasNonEmptySecondValues(const std::vector<ProfileValueDifference>& diff) {
return base::ranges::any_of(diff, [](const ProfileValueDifference& entry) {
return !entry.second_value.empty();
});
}
// Returns true if there is an entry coressponding to type ADDRESS_HOME_ADDRESS.
bool HasAddressEntry(const std::vector<ProfileValueDifference>& diff) {
return base::ranges::any_of(diff, [](const ProfileValueDifference& entry) {
return entry.type == ADDRESS_HOME_ADDRESS;
});
}
} // namespace
UpdateAddressProfileView::UpdateAddressProfileView(
std::unique_ptr<UpdateAddressBubbleController> controller,
views::View* anchor_view,
content::WebContents* web_contents)
: AddressBubbleBaseView(anchor_view, web_contents),
controller_(std::move(controller)) {
auto* layout_provider = views::LayoutProvider::Get();
SetAcceptCallback(base::BindOnce(
&UpdateAddressBubbleController::OnUserDecision,
base::Unretained(controller_.get()),
AutofillClient::AddressPromptUserDecision::kAccepted, std::nullopt));
SetCancelCallback(base::BindOnce(
&UpdateAddressBubbleController::OnUserDecision,
base::Unretained(controller_.get()),
AutofillClient::AddressPromptUserDecision::kDeclined, std::nullopt));
SetProperty(views::kElementIdentifierKey, kTopViewId);
SetTitle(controller_->GetWindowTitle());
SetButtonLabel(ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_OK_BUTTON_LABEL));
SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_CANCEL_BUTTON_LABEL));
SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kVertical)
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
.SetIgnoreDefaultMainAxisMargins(true)
.SetCollapseMargins(true)
.SetDefault(views::kMarginsKey,
gfx::Insets::VH(layout_provider->GetDistanceMetric(
DISTANCE_CONTROL_LIST_VERTICAL),
0));
std::vector<ProfileValueDifference> profile_diff = GetProfileDifferenceForUi(
controller_->GetProfileToSave(), controller_->GetOriginalProfile(),
g_browser_process->GetApplicationLocale());
std::u16string subtitle = GetProfileDescription(
controller_->GetOriginalProfile(),
g_browser_process->GetApplicationLocale(),
/*include_address_and_contacts=*/!HasAddressEntry(profile_diff));
if (!subtitle.empty()) {
views::Label* subtitle_label = AddChildView(std::make_unique<views::Label>(
subtitle, views::style::CONTEXT_LABEL, views::style::STYLE_SECONDARY));
subtitle_label->SetHorizontalAlignment(
gfx::HorizontalAlignment::ALIGN_LEFT);
}
auto* main_content_view =
AddChildView(std::make_unique<views::TableLayoutView>());
bool has_non_empty_original_values = HasNonEmptySecondValues(profile_diff);
// Build the TableLayoutView columns.
const int column_divider = layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
if (has_non_empty_original_values) {
// Label column exists only if there is a section for original values.
main_content_view
->AddColumn(
/*h_align=*/views::LayoutAlignment::kStart,
/*v_align=*/views::LayoutAlignment::kStart,
/*horizontal_resize=*/views::TableLayout::kFixedSize,
/*size_type=*/views::TableLayout::ColumnSize::kUsePreferred,
/*fixed_width=*/0, /*min_width=*/0)
.AddPaddingColumn(views::TableLayout::kFixedSize, column_divider);
}
main_content_view
->AddColumn(
/*h_align=*/views::LayoutAlignment::kStretch,
/*v_align=*/views::LayoutAlignment::kStretch,
/*horizontal_resize=*/1.0,
/*size_type=*/views::TableLayout::ColumnSize::kUsePreferred,
/*fixed_width=*/0, /*min_width=*/0)
.AddColumn(
/*h_align=*/views::LayoutAlignment::kStart,
/*v_align=*/views::LayoutAlignment::kStart,
/*horizontal_resize=*/views::TableLayout::kFixedSize,
/*size_type=*/views::TableLayout::ColumnSize::kUsePreferred,
/*fixed_width=*/0, /*min_width=*/0);
AddValuesRow(
main_content_view, profile_diff,
/*show_row_label=*/has_non_empty_original_values,
/*edit_button_callback=*/
base::BindRepeating(&UpdateAddressBubbleController::OnEditButtonClicked,
base::Unretained(controller_.get())));
if (has_non_empty_original_values) {
main_content_view->AddPaddingRow(
views::TableLayout::kFixedSize,
layout_provider->GetDistanceMetric(
DISTANCE_UNRELATED_CONTROL_VERTICAL_LARGE));
AddValuesRow(main_content_view, profile_diff, /*show_row_label=*/true,
/*edit_button_callback=*/{});
}
std::u16string footer_message = controller_->GetFooterMessage();
if (!footer_message.empty()) {
SetFootnoteView(
views::Builder<views::Label>()
.SetText(footer_message)
.SetTextContext(views::style::CONTEXT_BUBBLE_FOOTER)
.SetTextStyle(views::style::STYLE_SECONDARY)
.SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
.SetMultiLine(true)
.Build());
}
set_fixed_width(std::max(
main_content_view->GetPreferredSize().width() + margins().width(),
layout_provider->GetDistanceMetric(
views::DISTANCE_BUBBLE_PREFERRED_WIDTH)));
}
UpdateAddressProfileView::~UpdateAddressProfileView() = default;
bool UpdateAddressProfileView::ShouldShowCloseButton() const {
return true;
}
std::u16string UpdateAddressProfileView::GetWindowTitle() const {
return controller_->GetWindowTitle();
}
void UpdateAddressProfileView::WindowClosing() {
if (controller_) {
controller_->OnBubbleClosed();
controller_ = nullptr;
}
}
void UpdateAddressProfileView::Show(DisplayReason reason) {
ShowForReason(reason);
}
void UpdateAddressProfileView::Hide() {
CloseBubble();
// If |controller_| is null, WindowClosing() won't invoke OnBubbleClosed(), so
// do that here. This will clear out |controller_|'s reference to |this|. Note
// that WindowClosing() happens only after the _asynchronous_ Close() task
// posted in CloseBubble() completes, but we need to fix references sooner.
if (controller_)
controller_->OnBubbleClosed();
controller_ = nullptr;
}
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(UpdateAddressProfileView, kTopViewId);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(UpdateAddressProfileView,
kEditButtonViewId);
} // namespace autofill