chromium/chrome/browser/ui/chromeos/magic_boost/magic_boost_opt_in_card.cc

// Copyright 2024 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/chromeos/magic_boost/magic_boost_opt_in_card.h"

#include <string>

#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_card_controller.h"
#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_constants.h"
#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_metrics.h"
#include "chrome/browser/ui/views/editor_menu/utils/utils.h"
#include "chromeos/components/magic_boost/public/cpp/magic_boost_state.h"
#include "chromeos/crosapi/mojom/magic_boost.mojom.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/background.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/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"

namespace chromeos {

namespace {

// Widget constants
constexpr char kWidgetName[] = "MagicBoostOptInWidget";

// Card constants
constexpr gfx::Insets kInteriorMargin = gfx::Insets(16);
constexpr float kCornerRadius = 8.0f;

// Label constants
constexpr int kTitleLabelMaxLines = 2;
constexpr int kBodyLabelMaxLines = 3;

// Image constants
constexpr int kImageViewSize = 36;
constexpr int kImageViewCornerRadius = 12;
constexpr int kImageViewIconSize = 20;

// Button constants
constexpr int kButtonsContainerHeight = 32;

// Spacing constants
constexpr int kBetweenButtonsSpacing = 8;
constexpr int kBetweenImageAndTextSpacing = 16;
constexpr int kBetweenContentsAndButtonsSpacing = 16;
constexpr int kBetweenLabelsSpacing = 4;

// Font lists
const gfx::FontList kBodyTextFontList =
    gfx::FontList({"Google Sans", "Roboto"},
                  gfx::Font::NORMAL,
                  /*font_size=*/12,
                  gfx::Font::Weight::NORMAL);
const gfx::FontList kTitleTextFontList =
    gfx::FontList({"Google Sans", "Roboto"},
                  gfx::Font::NORMAL,
                  /*font_size=*/14,
                  gfx::Font::Weight::MEDIUM);

}  // namespace

// MagicBoostOptInCard --------------------------------------------------------

MagicBoostOptInCard::MagicBoostOptInCard(MagicBoostCardController* controller)
    : chromeos::editor_menu::PreTargetHandlerView(
          /*card_type=*/editor_menu::CardType::kMagicBoostOptInCard),
      controller_(controller) {
  SetLayoutManager(std::make_unique<views::FlexLayout>())
      ->SetOrientation(views::LayoutOrientation::kVertical)
      .SetInteriorMargin(kInteriorMargin)
      .SetDefault(views::kMarginsKey,
                  gfx::Insets::VH(kBetweenContentsAndButtonsSpacing, 0))
      .SetCollapseMargins(true)
      .SetIgnoreDefaultMainAxisMargins(true);
  SetBackground(
      views::CreateThemedSolidBackground(ui::kColorPrimaryBackground));

  // Painted to layer so view can be semi-transparent and set rounded corners.
  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);
  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kCornerRadius));

  // Create image and text container and add an image view.
  auto* image_and_text_container = AddChildView(
      views::Builder<views::FlexLayoutView>()
          .SetOrientation(views::LayoutOrientation::kHorizontal)
          .SetCrossAxisAlignment(views::LayoutAlignment::kStart)
          .CustomConfigure(base::BindOnce([](views::FlexLayoutView* layout) {
            layout->SetDefault(views::kMarginsKey,
                               gfx::Insets::VH(0, kBetweenImageAndTextSpacing));
          }))
          .SetCollapseMargins(true)
          .SetIgnoreDefaultMainAxisMargins(true)
          // Set FlexSpecification to `kUnbounded` so the body text can take up
          // more height when it's multi-line.
          .SetProperty(
              views::kFlexBehaviorKey,
              views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
                                       views::MaximumFlexSizeRule::kUnbounded,
                                       /*adjust_height_for_width=*/true))
          .AddChildren(
              views::Builder<views::ImageView>()
                  .SetPreferredSize(gfx::Size(kImageViewSize, kImageViewSize))
                  .SetImage(ui::ImageModel::FromVectorIcon(
                      kMahiSparkIcon, ui::kColorSysOnPrimaryContainer,
                      kImageViewIconSize))
                  .SetBackground(views::CreateThemedSolidBackground(
                      ui::kColorSysPrimaryContainer))
                  // Painted to layer to set rounded corners.
                  .SetPaintToLayer()
                  .CustomConfigure(
                      base::BindOnce([](views::ImageView* image_view) {
                        image_view->layer()->SetFillsBoundsOpaquely(false);
                        image_view->layer()->SetRoundedCornerRadius(
                            gfx::RoundedCornersF(kImageViewCornerRadius));
                      })))
          .Build());

  // Create text container that holds title and body text.
  bool include_orca =
      controller_->GetOptInFeatures() == OptInFeatures::kOrcaAndHmr;
  image_and_text_container->AddChildView(
      views::Builder<views::FlexLayoutView>()
          .SetOrientation(views::LayoutOrientation::kVertical)
          .CustomConfigure(base::BindOnce([](views::FlexLayoutView* layout) {
            layout->SetDefault(views::kMarginsKey,
                               gfx::Insets::VH(kBetweenLabelsSpacing, 0));
          }))
          .SetCollapseMargins(true)
          .SetIgnoreDefaultMainAxisMargins(true)
          // Set FlexSpecification to `kUnbounded` so the body text can
          // take up more height when it's multi-line.
          .SetProperty(
              views::kFlexBehaviorKey,
              views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
                                       views::MaximumFlexSizeRule::kUnbounded,
                                       /*adjust_height_for_width=*/true))
          .AddChildren(
              views::Builder<views::Label>()
                  .CopyAddressTo(&title_label_)
                  .SetID(magic_boost::ViewId::OptInCardTitleLabel)
                  .SetText(l10n_util::GetStringUTF16(
                      IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_TITLE))
                  .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                  .SetEnabledColorId(ui::kColorSysOnSurface)
                  .SetAutoColorReadabilityEnabled(false)
                  .SetSubpixelRenderingEnabled(false)
                  .SetText(l10n_util::GetStringUTF16(
                      include_orca
                          ? IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_TITLE
                          : IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_NO_ORCA_TITLE))
                  .SetFontList(kTitleTextFontList)
                  .SetMultiLine(true)
                  .SetMaxLines(kTitleLabelMaxLines),
              views::Builder<views::Label>()
                  .CopyAddressTo(&body_label_)
                  .SetID(magic_boost::ViewId::OptInCardBodyLabel)
                  .SetText(l10n_util::GetStringUTF16(
                      IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_BODY))
                  .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                  .SetEnabledColorId(ui::kColorSysOnSurface)
                  .SetAutoColorReadabilityEnabled(false)
                  .SetSubpixelRenderingEnabled(false)
                  .SetText(l10n_util::GetStringUTF16(
                      include_orca
                          ? IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_BODY
                          : IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_NO_ORCA_BODY))
                  .SetFontList(kBodyTextFontList)
                  .SetMultiLine(true)
                  .SetMaxLines(kBodyLabelMaxLines))
          .Build());

  // Create buttons container that holds two buttons.
  std::u16string decline_button_text =
      l10n_util::GetStringUTF16(IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_DECLINE_BUTTON);
  std::u16string accept_button_text =
      l10n_util::GetStringUTF16(IDS_ASH_MAGIC_BOOST_OPT_IN_CARD_ACCEPT_BUTTON);
  AddChildView(
      views::Builder<views::BoxLayoutView>()
          .SetMainAxisAlignment(views::LayoutAlignment::kEnd)
          .SetBetweenChildSpacing(kBetweenButtonsSpacing)
          // Set a preferred size so buttons can adjust to the desired height,
          // instead of the default height set by the `MdTextButton` class.
          .SetPreferredSize(gfx::Size(image_and_text_container->width(),
                                      kButtonsContainerHeight))
          .AddChildren(views::Builder<views::MdTextButton>()
                           .CopyAddressTo(&secondary_button_)
                           .SetID(magic_boost::ViewId::OptInCardSecondaryButton)
                           .SetText(decline_button_text)
                           .SetAccessibleName(decline_button_text)
                           .SetStyle(ui::ButtonStyle::kText)
                           .SetCallback(base::BindRepeating(
                               &MagicBoostOptInCard::OnSecondaryButtonPressed,
                               weak_ptr_factory_.GetWeakPtr())),
                       views::Builder<views::MdTextButton>()
                           .SetID(magic_boost::ViewId::OptInCardPrimaryButton)
                           .SetText(accept_button_text)
                           .SetAccessibleName(accept_button_text)
                           .SetStyle(ui::ButtonStyle::kProminent)
                           .SetCallback(base::BindRepeating(
                               &MagicBoostOptInCard::OnPrimaryButtonPressed,
                               weak_ptr_factory_.GetWeakPtr())))
          .Build());
}

MagicBoostOptInCard::~MagicBoostOptInCard() = default;

// static
views::UniqueWidgetPtr MagicBoostOptInCard::CreateWidget(
    MagicBoostCardController* controller,
    const gfx::Rect& anchor_view_bounds) {
  views::Widget::InitParams params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_POPUP);
  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  params.activatable = views::Widget::InitParams::Activatable::kYes;
  params.shadow_elevation = 2;
  params.shadow_type = views::Widget::InitParams::ShadowType::kDrop;
  params.z_order = ui::ZOrderLevel::kFloatingUIElement;
  params.name = GetWidgetName();

  views::UniqueWidgetPtr widget =
      std::make_unique<views::Widget>(std::move(params));
  MagicBoostOptInCard* magic_boost_opt_in_card = widget->SetContentsView(
      std::make_unique<MagicBoostOptInCard>(controller));
  magic_boost_opt_in_card->UpdateWidgetBounds(anchor_view_bounds);

  return widget;
}

// static
const char* MagicBoostOptInCard::GetWidgetName() {
  return kWidgetName;
}

void MagicBoostOptInCard::UpdateWidgetBounds(
    const gfx::Rect& anchor_view_bounds) {
  // TODO(b/318733414): Move `GetEditorMenuBounds` to a common place to use.
  GetWidget()->SetBounds(
      editor_menu::GetEditorMenuBounds(anchor_view_bounds, this));
}

void MagicBoostOptInCard::RequestFocus() {
  views::View::RequestFocus();
  secondary_button_->RequestFocus();
}

// static
const char* MagicBoostOptInCard::GetWidgetNameForTest() {
  return kWidgetName;
}

void MagicBoostOptInCard::OnPrimaryButtonPressed() {
  magic_boost::RecordOptInCardActionMetrics(
      controller_->GetOptInFeatures(),
      magic_boost::OptInCardAction::kAcceptButtonPressed);

  controller_->CloseOptInUi();

  controller_->ShowDisclaimerUi(/*display_id=*/
                                display::Screen::GetScreen()
                                    ->GetDisplayNearestWindow(
                                        GetWidget()->GetNativeWindow())
                                    .id());

#if BUILDFLAG(IS_CHROMEOS_ASH)
  auto* magic_boost_state = chromeos::MagicBoostState::Get();
  magic_boost_state->AsyncWriteConsentStatus(
      chromeos::HMRConsentStatus::kPendingDisclaimer);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

void MagicBoostOptInCard::OnSecondaryButtonPressed() {
  magic_boost::RecordOptInCardActionMetrics(
      controller_->GetOptInFeatures(),
      magic_boost::OptInCardAction::kDeclineButtonPressed);

  controller_->CloseOptInUi();

#if BUILDFLAG(IS_CHROMEOS_ASH)
  auto* magic_boost_state = chromeos::MagicBoostState::Get();
  if (controller_->GetOptInFeatures() == OptInFeatures::kOrcaAndHmr) {
    magic_boost_state->DisableOrcaFeature();
  }
  magic_boost_state->AsyncWriteConsentStatus(
      chromeos::HMRConsentStatus::kDeclined);
  magic_boost_state->AsyncWriteHMREnabled(/*enabled=*/false);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

BEGIN_METADATA(MagicBoostOptInCard)
END_METADATA

}  // namespace chromeos