blob: d0e722d4045d8259588d0bf52e26374ba994e8bb [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/edit_address_profile_view.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/autofill/address_editor_controller.h"
#include "chrome/browser/ui/autofill/autofill_bubble_base.h"
#include "chrome/browser/ui/autofill/edit_address_profile_dialog_controller.h"
#include "chrome/browser/ui/autofill/edit_address_profile_view.h"
#include "chrome/browser/ui/tabs/public/tab_dialog_manager.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/views/autofill/address_editor_view.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/strings/grit/components_strings.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
namespace autofill {
namespace {
class AutofillBubbleUI : public AutofillBubbleBase {
public:
AutofillBubbleUI(std::unique_ptr<views::Widget> dialog,
EditAddressProfileView* profile_view);
AutofillBubbleUI(const AutofillBubbleUI&) = delete;
AutofillBubbleUI& operator=(const AutofillBubbleUI&) = delete;
~AutofillBubbleUI() override;
private:
// Overrides from AutofillBubbleBase:
void Hide() override;
bool IsMouseHovered() const override;
void CloseWidget(views::Widget::ClosedReason closed_reason);
std::unique_ptr<views::Widget> dialog_;
raw_ptr<EditAddressProfileView> profile_view_ = nullptr;
};
AutofillBubbleUI::AutofillBubbleUI(std::unique_ptr<views::Widget> dialog,
EditAddressProfileView* profile_view)
: dialog_(std::move(dialog)), profile_view_(profile_view) {
dialog_->MakeCloseSynchronous(
base::BindOnce(&AutofillBubbleUI::CloseWidget, base::Unretained(this)));
}
AutofillBubbleUI::~AutofillBubbleUI() = default;
void AutofillBubbleUI::Hide() {
dialog_->Close();
}
bool AutofillBubbleUI::IsMouseHovered() const {
// The edit view is not part of the bubbles managed by `BubbleManager`.
NOTREACHED();
}
void AutofillBubbleUI::CloseWidget(views::Widget::ClosedReason closed_reason) {
// We need to hold the dialog here so it remains alive long enough for the
// stack to be cleaned up from the WidgetClosed() call. This keeps potential
// dangling pointer checks from triggering as the stack unwinds.
auto dialog = std::move(dialog_);
profile_view_->WidgetClosed();
dialog.reset();
}
} // namespace
std::unique_ptr<AutofillBubbleBase> ShowEditAddressProfileDialogView(
content::WebContents* web_contents,
EditAddressProfileDialogController* controller) {
auto* dialog = new EditAddressProfileView(controller);
dialog->ShowForWebContents(web_contents);
tabs::TabInterface* tab_interface =
tabs::TabInterface::GetFromContents(web_contents);
auto widget =
tab_interface->GetTabFeatures()
->tab_dialog_manager()
->CreateAndShowDialog(
dialog, std::make_unique<tabs::TabDialogManager::Params>());
dialog->RequestFocus();
return std::make_unique<AutofillBubbleUI>(std::move(widget), dialog);
}
EditAddressProfileView::EditAddressProfileView(
EditAddressProfileDialogController* controller)
: controller_(controller) {
DCHECK(controller);
// TODO(crbug.com/338254375): Remove the following line once this is the
// default state for widgets.
SetOwnershipOfNewWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET);
SetButtons(static_cast<int>(ui::mojom::DialogButton::kOk) |
static_cast<int>(ui::mojom::DialogButton::kCancel));
SetModalType(ui::mojom::ModalType::kChild);
SetShowCloseButton(false);
set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
SetAcceptCallbackWithClose(base::BindRepeating(
&EditAddressProfileView::OnAcceptButtonClicked, base::Unretained(this)));
SetCancelCallback(base::BindOnce(
&EditAddressProfileView::OnUserDecision, base::Unretained(this),
AutofillClient::AddressPromptUserDecision::kEditDeclined));
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
views::LayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_VERTICAL)));
set_margins(ChromeLayoutProvider::Get()->GetInsetsMetric(
views::InsetsMetric::INSETS_DIALOG));
SetProperty(views::kElementIdentifierKey, kTopViewId);
SetTitle(controller_->GetWindowTitle());
SetButtonLabel(ui::mojom::DialogButton::kOk, controller_->GetOkButtonLabel());
SetButtonLabel(ui::mojom::DialogButton::kCancel,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_EDIT_ADDRESS_DIALOG_CANCEL_BUTTON_LABEL));
}
EditAddressProfileView::~EditAddressProfileView() = default;
void EditAddressProfileView::ShowForWebContents(
content::WebContents* web_contents) {
DCHECK(web_contents);
auto address_editor_controller = std::make_unique<AddressEditorController>(
controller_->GetProfileToEdit(),
autofill::PersonalDataManagerFactory::GetForBrowserContext(
web_contents->GetBrowserContext()),
controller_->GetIsValidatable());
// Storing subscription (which gets canceled in the destructor) in a property
// secures using of Unretained(this).
on_is_valid_change_subscription_ =
address_editor_controller->AddIsValidChangedCallback(
base::BindRepeating(&EditAddressProfileView::UpdateActionButtonState,
base::Unretained(this)));
address_editor_view_ = AddChildView(std::make_unique<AddressEditorView>(
std::move(address_editor_controller)));
const std::u16string& footer_message = controller_->GetFooterMessage();
if (!footer_message.empty()) {
AddChildView(
views::Builder<views::Label>()
.SetText(footer_message)
.SetTextStyle(views::style::STYLE_SECONDARY)
.SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
.SetMultiLine(true)
.Build());
}
}
views::View* EditAddressProfileView::GetInitiallyFocusedView() {
return address_editor_view_ ? address_editor_view_->initial_focus_view()
: nullptr;
}
void EditAddressProfileView::WidgetClosed() {
if (controller_) {
std::exchange(controller_, nullptr)
->OnDialogClosed(
decision_,
decision_ ==
AutofillClient::AddressPromptUserDecision::kEditAccepted
? base::optional_ref(address_editor_view_->GetAddressProfile())
: std::nullopt);
}
}
void EditAddressProfileView::ChildPreferredSizeChanged(views::View* child) {
const int width = fixed_width();
GetWidget()->SetSize(gfx::Size(width, GetHeightForWidth(width)));
}
AddressEditorView* EditAddressProfileView::GetAddressEditorViewForTesting() {
return address_editor_view_;
}
void EditAddressProfileView::OnUserDecision(
AutofillClient::AddressPromptUserDecision decision) {
decision_ = decision;
}
void EditAddressProfileView::UpdateActionButtonState(bool is_valid) {
SetButtonEnabled(ui::mojom::DialogButton::kOk, is_valid);
}
bool EditAddressProfileView::OnAcceptButtonClicked() {
bool is_form_valid = address_editor_view_->ValidateAllFields();
if (is_form_valid) {
OnUserDecision(AutofillClient::AddressPromptUserDecision::kEditAccepted);
}
return is_form_valid;
}
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(EditAddressProfileView, kTopViewId);
} // namespace autofill