chromium/ash/assistant/ui/main_stage/assistant_opt_in_view.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/assistant/ui/main_stage/assistant_opt_in_view.h"

#include <memory>
#include <utility>
#include <vector>

#include "ash/assistant/ui/assistant_ui_constants.h"
#include "ash/assistant/ui/assistant_view_delegate.h"
#include "ash/assistant/ui/assistant_view_ids.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/functional/bind.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout.h"

namespace ash {

namespace {

// Appearance.
constexpr int kPreferredHeightDip = 32;

// Helpers ---------------------------------------------------------------------

views::StyledLabel::RangeStyleInfo CreateStyleInfo(
    gfx::Font::Weight weight = gfx::Font::Weight::NORMAL) {
  views::StyledLabel::RangeStyleInfo style;
  style.custom_font = assistant::ui::GetDefaultFontList()
                          .DeriveWithSizeDelta(2)
                          .DeriveWithWeight(weight);
  style.override_color = SK_ColorWHITE;
  return style;
}

std::u16string GetAction(int consent_status) {
  return consent_status == assistant::prefs::ConsentStatus::kUnauthorized
             ? l10n_util::GetStringUTF16(
                   IDS_ASH_ASSISTANT_OPT_IN_ASK_ADMINISTRATOR)
             : l10n_util::GetStringUTF16(IDS_ASH_ASSISTANT_OPT_IN_GET_STARTED);
}

// AssistantOptInContainer -----------------------------------------------------

class AssistantOptInContainer : public views::Button {
  METADATA_HEADER(AssistantOptInContainer, views::Button)

 public:
  explicit AssistantOptInContainer(views::Button::PressedCallback callback)
      : views::Button(std::move(callback)) {
    constexpr float kHighlightOpacity = 0.06f;
    SetFocusPainter(views::Painter::CreateSolidRoundRectPainter(
        SkColorSetA(SK_ColorBLACK, 0xff * kHighlightOpacity),
        kPreferredHeightDip / 2));
  }

  AssistantOptInContainer(const AssistantOptInContainer&) = delete;

  AssistantOptInContainer& operator=(const AssistantOptInContainer) = delete;

  ~AssistantOptInContainer() override = default;

  // views::View:
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override {
    const int preferred_width =
        views::View::CalculatePreferredSize(available_size).width();
    return gfx::Size(preferred_width, kPreferredHeightDip);
  }

  void ChildPreferredSizeChanged(views::View* child) override {
    PreferredSizeChanged();
  }

  void OnPaintBackground(gfx::Canvas* canvas) override {
    cc::PaintFlags flags;
    flags.setAntiAlias(true);
    flags.setColor(gfx::kGoogleBlue500);
    canvas->DrawRoundRect(GetContentsBounds(), height() / 2, flags);
  }
};

BEGIN_METADATA(AssistantOptInContainer)
END_METADATA

}  // namespace

// AssistantOptInView ----------------------------------------------------------

AssistantOptInView::AssistantOptInView(AssistantViewDelegate* delegate)
    : delegate_(delegate) {
  SetID(AssistantViewID::kOptInView);
  InitLayout();
  AssistantState::Get()->AddObserver(this);
}

AssistantOptInView::~AssistantOptInView() {
  AssistantState::Get()->RemoveObserver(this);
}

void AssistantOptInView::ChildPreferredSizeChanged(views::View* child) {
  PreferredSizeChanged();
}

void AssistantOptInView::OnAssistantConsentStatusChanged(int consent_status) {
  UpdateLabel(consent_status);
}

void AssistantOptInView::InitLayout() {
  views::BoxLayout* layout_manager =
      SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kHorizontal));

  layout_manager->set_cross_axis_alignment(
      views::BoxLayout::CrossAxisAlignment::kCenter);

  layout_manager->set_main_axis_alignment(
      views::BoxLayout::MainAxisAlignment::kCenter);

  // Container.
  container_ = AddChildView(
      std::make_unique<AssistantOptInContainer>(base::BindRepeating(
          &AssistantOptInView::OnButtonPressed, base::Unretained(this))));

  layout_manager =
      container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
          views::BoxLayout::Orientation::kHorizontal,
          gfx::Insets::VH(0, assistant::ui::kHorizontalPadding)));

  layout_manager->set_cross_axis_alignment(
      views::BoxLayout::CrossAxisAlignment::kCenter);

  // Label.
  label_ = container_->AddChildView(std::make_unique<views::StyledLabel>());
  label_->SetID(AssistantViewID::kOptInViewStyledLabel);
  label_->SetAutoColorReadabilityEnabled(false);
  label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);

  UpdateLabel(AssistantState::Get()->consent_status().value_or(
      assistant::prefs::ConsentStatus::kUnknown));
}

void AssistantOptInView::UpdateLabel(int consent_status) {
  // First substitution string: "Unlock more Assistant features."
  const std::u16string unlock_features =
      l10n_util::GetStringUTF16(IDS_ASH_ASSISTANT_OPT_IN_UNLOCK_MORE_FEATURES);

  // Second substitution string specifies the action to be taken.
  const std::u16string action = GetAction(consent_status);

  // Set the text, having replaced placeholders in the opt in prompt with
  // substitution strings and caching their offset positions for styling.
  std::vector<size_t> offsets;
  auto label_text = l10n_util::GetStringFUTF16(
      IDS_ASH_ASSISTANT_OPT_IN_PROMPT, unlock_features, action, &offsets);
  label_->SetText(label_text);

  // Style the first substitution string.
  label_->AddStyleRange(
      gfx::Range(offsets.at(0), offsets.at(0) + unlock_features.length()),
      CreateStyleInfo());

  // Style the second substitution string.
  label_->AddStyleRange(
      gfx::Range(offsets.at(1), offsets.at(1) + action.length()),
      CreateStyleInfo(gfx::Font::Weight::BOLD));

  container_->GetViewAccessibility().SetName(label_text);

  // After updating the |label_| we need to ensure that it is remeasured and
  // repainted to address a timing bug in which the AssistantOptInView was
  // sometimes drawn in an invalid state (b/130758812).
  container_->DeprecatedLayoutImmediately();
  container_->SchedulePaint();
}

void AssistantOptInView::OnButtonPressed() {
  delegate_->OnOptInButtonPressed();
}

BEGIN_METADATA(AssistantOptInView)
END_METADATA

}  // namespace ash