blob: 7cadc10abe5442ca6be49a0107b05a51da6779ee [file] [log] [blame]
// Copyright 2022 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/commerce/price_tracking_view.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/commerce/shopping_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/price_tracking_utils.h"
#include "components/omnibox/browser/vector_icons.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets_outsets_base.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/toggle_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_provider.h"
namespace {
constexpr int kIconSize = 20;
constexpr int kIconMargin = 8;
int GetIconMargin() {
return kIconMargin;
}
gfx::Size GetIconSize() {
return gfx::Size(kIconSize, kIconSize);
}
} // namespace
PriceTrackingView::PriceTrackingView(Profile* profile,
const GURL& page_url,
bool is_price_track_enabled,
const commerce::ProductInfo& product_info)
: profile_(profile),
is_price_track_enabled_(is_price_track_enabled),
product_info_(product_info) {
auto* layout_provider = views::LayoutProvider::Get();
const int label_spacing = 0;
const auto label_context = views::style::CONTEXT_LABEL;
const int horizontal_spacing = layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
const gfx::Insets dialog_insets =
layout_provider->GetInsetsMetric(views::INSETS_DIALOG);
SetCrossAxisAlignment(views::LayoutAlignment::kStart);
// Icon column
auto* icon = AddChildView(std::make_unique<views::ImageView>());
icon->SetImage(ui::ImageModel::FromVectorIcon(
omnibox::kPriceTrackingDisabledIcon, kColorBookmarkDialogTrackPriceIcon));
icon->SetImageSize(GetIconSize());
icon->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(0, 0, 0, GetIconMargin()));
// Text column
auto text_container = std::make_unique<views::FlexLayoutView>();
text_container->SetOrientation(views::LayoutOrientation::kVertical);
// Title label
auto* title_label =
text_container->AddChildView(std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_OMNIBOX_TRACK_PRICE_DIALOG_TITLE),
label_context, views::style::STYLE_EMPHASIZED));
title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title_label->SetFocusBehavior(View::FocusBehavior::ACCESSIBLE_ONLY);
// Body label
int body_string_id = IDS_BOOKMARK_STAR_DIALOG_TRACK_PRICE_DESCRIPTION;
// If "track by default" is enabled, the email consent comes after enabling
// price tracking for the first time, so it doesn't need to be included in the
// "email disabled" state. If we're not in the experiment, only show the
// "email disabled" message if it is explicitly disabled (it's turned on
// implicitly on when the first item is tracked).
bool email_pref_set_by_user =
commerce::IsEmailNotificationPrefSetByUser(profile_->GetPrefs());
bool email_pref_value =
commerce::GetEmailNotificationPrefValue(profile_->GetPrefs());
if (!email_pref_set_by_user ||
(email_pref_set_by_user && !email_pref_value)) {
body_string_id = IDS_BOOKMARK_STAR_DIALOG_TRACK_PRICE_DESCRIPTION_EMAIL_OFF;
}
body_label_ = text_container->AddChildView(std::make_unique<views::Label>(
l10n_util::GetStringUTF16(body_string_id), label_context,
views::style::STYLE_SECONDARY));
body_label_->SetFontList(body_label_->font_list().DeriveWithSizeDelta(-1));
body_label_->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(label_spacing, 0, 0, 0));
body_label_->SetMultiLine(true);
body_label_->SetAllowCharacterBreak(true);
body_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
body_label_->SetFocusBehavior(View::FocusBehavior::ACCESSIBLE_ONLY);
AddChildView(std::move(text_container));
// Toggle button column
toggle_button_ = AddChildView(std::make_unique<views::ToggleButton>(
base::BindRepeating(&PriceTrackingView::OnToggleButtonPressed,
weak_ptr_factory_.GetWeakPtr(), page_url)));
toggle_button_->SetIsOn(is_price_track_enabled_);
scoped_observation_.Observe(
commerce::ShoppingServiceFactory::GetForBrowserContext(profile_));
toggle_button_->GetViewAccessibility().SetName(GetToggleAccessibleName());
toggle_button_->SetProperty(views::kMarginsKey,
gfx::Insets::TLBR(0, horizontal_spacing, 0, 0));
const int bubble_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_BUBBLE_PREFERRED_WIDTH);
int label_width;
toggle_button_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kPreferred)
.WithAlignment(views::LayoutAlignment::kEnd));
label_width = bubble_width - horizontal_spacing - dialog_insets.left() -
dialog_insets.right() - GetIconMargin() -
GetIconSize().width() -
toggle_button_->GetPreferredSize().width();
body_label_->SizeToFit(label_width);
base::RecordAction(base::UserMetricsAction(
"Commerce.PriceTracking.BookmarkDialogPriceTrackViewShown"));
}
PriceTrackingView::~PriceTrackingView() = default;
bool PriceTrackingView::IsToggleOn() {
return toggle_button_->GetIsOn();
}
void PriceTrackingView::OnSubscribe(const commerce::CommerceSubscription& sub,
bool succeeded) {
if (succeeded) {
HandleSubscriptionUpdate(sub, true);
}
}
void PriceTrackingView::OnUnsubscribe(const commerce::CommerceSubscription& sub,
bool succeeded) {
if (succeeded) {
HandleSubscriptionUpdate(sub, false);
}
}
void PriceTrackingView::HandleSubscriptionUpdate(
const commerce::CommerceSubscription& sub,
bool is_tracking) {
if (sub.id_type == commerce::IdentifierType::kProductClusterId &&
base::NumberToString(product_info_.product_cluster_id.value_or(
commerce::kInvalidSubscriptionId)) == sub.id) {
is_price_track_enabled_ = is_tracking;
toggle_button_->SetIsOn(is_tracking);
toggle_button_->GetViewAccessibility().SetName(GetToggleAccessibleName());
}
}
std::u16string PriceTrackingView::GetToggleAccessibleName() {
return l10n_util::GetStringUTF16(
IDS_PRICE_TRACKING_TRACK_PRODUCT_ACCESSIBILITY);
}
void PriceTrackingView::OnToggleButtonPressed(const GURL& url) {
is_price_track_enabled_ = !is_price_track_enabled_;
if (is_price_track_enabled_) {
base::RecordAction(base::UserMetricsAction(
"Commerce.PriceTracking.BookmarkDialogPriceTrackViewTrackedPrice"));
} else {
base::RecordAction(base::UserMetricsAction(
"Commerce.PriceTracking.BookmarkDialogPriceTrackViewUntrackedPrice"));
}
toggle_button_->GetViewAccessibility().SetName(GetToggleAccessibleName());
UpdatePriceTrackingState(url);
}
void PriceTrackingView::UpdatePriceTrackingState(const GURL& url) {
bookmarks::BookmarkModel* const model =
BookmarkModelFactory::GetForBrowserContext(profile_);
const bookmarks::BookmarkNode* node =
model->GetMostRecentlyAddedUserNodeForURL(url);
// If "track by default" is on, we'll show a dialog after saving to offer
// email notifications.
if (profile_ && is_price_track_enabled_) {
commerce::MaybeEnableEmailNotifications(profile_->GetPrefs());
}
commerce::ShoppingService* service =
commerce::ShoppingServiceFactory::GetForBrowserContext(profile_);
base::OnceCallback<void(bool)> callback =
base::BindOnce(&PriceTrackingView::OnPriceTrackingStateUpdated,
weak_ptr_factory_.GetWeakPtr());
if (node) {
commerce::SetPriceTrackingStateForBookmark(
service, model, node, is_price_track_enabled_, std::move(callback));
} else {
DCHECK(!is_price_track_enabled_);
if (commerce::CanTrackPrice(product_info_)) {
commerce::SetPriceTrackingStateForClusterId(
service, model, product_info_.product_cluster_id.value(),
is_price_track_enabled_, std::move(callback));
}
}
}
void PriceTrackingView::OnPriceTrackingStateUpdated(bool success) {
// TODO(crbug.com/40232577): Record latency for the update status.
if (!success) {
is_price_track_enabled_ = !is_price_track_enabled_;
toggle_button_->SetIsOn(is_price_track_enabled_);
toggle_button_->GetViewAccessibility().SetName(GetToggleAccessibleName());
body_label_->SetText(l10n_util::GetStringUTF16(
IDS_OMNIBOX_TRACK_PRICE_DIALOG_ERROR_DESCRIPTION));
}
}
BEGIN_METADATA(PriceTrackingView)
END_METADATA