chromium/chrome/browser/ui/quick_answers/ui/result_view.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/quick_answers/ui/result_view.h"

#include <memory>
#include <string>
#include <string_view>

#include "base/functional/bind.h"
#include "chrome/browser/ui/quick_answers/ui/typography.h"
#include "chromeos/components/quick_answers/public/cpp/constants.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"

namespace quick_answers {
namespace {
// Phonetics audio button.
// TODO(b/335701090): Use LayoutProvider.
constexpr auto kPhoneticsAudioButtonMarginInsets =
    gfx::Insets::TLBR(0, 4, 0, 4);
constexpr int kPhoneticsAudioButtonSizeDip = 14;
constexpr int kPhoneticsAudioButtonBorderDip = 3;

constexpr int kItemSpacing = 4;

constexpr char16_t kSeparatorText[] = u" ยท ";

views::Builder<views::ImageButton> PhoneticsAudioButton() {
  return views::Builder<views::ImageButton>()
      .SetImageModel(
          views::Button::ButtonState::STATE_NORMAL,
          ui::ImageModel::FromVectorIcon(vector_icons::kVolumeUpIcon,
                                         ui::kColorButtonBackgroundProminent,
                                         kPhoneticsAudioButtonSizeDip))
      .SetTooltipText(l10n_util::GetStringUTF16(
          IDS_RICH_ANSWERS_VIEW_PHONETICS_BUTTON_A11Y_NAME_TEXT))
      .SetBorder(views::CreateEmptyBorder(kPhoneticsAudioButtonBorderDip));
}

bool IsEmpty(PhoneticsInfo phonetics_info) {
  return phonetics_info.phonetics_audio.is_empty();
}

}  // namespace

ResultView::ResultView() {
  SetOrientation(views::LayoutOrientation::kVertical);
  SetCollapseMargins(true);

  AddChildView(
      views::Builder<views::FlexLayoutView>()
          .CopyAddressTo(&flex_layout_view_)
          .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
          .SetProperty(views::kMarginsKey,
                       gfx::Insets::TLBR(0, 0, kItemSpacing, 0))
          .AddChild(
              views::Builder<views::Label>()
                  .CopyAddressTo(&first_line_label_)
                  .SetVisible(false)
                  .SetEnabledColorId(ui::kColorLabelForeground)
                  .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
                  // Set lower priority order for `first_line_label` compared to
                  // `first_line_sub_label` as primary text gets elided first
                  // if a sub text is shown.
                  .SetProperty(
                      views::kFlexBehaviorKey,
                      views::FlexSpecification(
                          views::MinimumFlexSizeRule::kScaleToMinimumSnapToZero)
                          .WithOrder(2)))
          .AddChild(views::Builder<views::Label>()
                        .CopyAddressTo(&separator_label_)
                        .SetVisible(false)
                        .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                        .SetEnabledColorId(ui::kColorLabelForeground)
                        .SetText(kSeparatorText))
          .AddChild(
              views::Builder<views::Label>()
                  .CopyAddressTo(&first_line_sub_label_)
                  .SetVisible(false)
                  .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
                  .SetEnabledColorId(ui::kColorLabelForeground)
                  .SetProperty(
                      views::kFlexBehaviorKey,
                      views::FlexSpecification(
                          views::MinimumFlexSizeRule::kScaleToMinimumSnapToZero)
                          .WithOrder(1)))
          .AddChild(PhoneticsAudioButton()
                        .CopyAddressTo(&phonetics_audio_button_)
                        .SetVisible(false)
                        .SetProperty(views::kMarginsKey,
                                     kPhoneticsAudioButtonMarginInsets)
                        .SetCallback(base::BindRepeating(
                            &ResultView::OnPhoneticsAudioButtonPressed,
                            base::Unretained(this))))
          .Build());

  AddChildView(
      views::Builder<views::Label>()
          .CopyAddressTo(&second_line_label_)
          .SetVisible(false)
          .SetEnabledColorId(ui::kColorLabelForegroundSecondary)
          .SetMultiLine(true)
          .SetMaxLines(kMaxLines)
          .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
          .SetProperty(views::kFlexBehaviorKey,
                       views::FlexSpecification(
                           views::MinimumFlexSizeRule::kPreferred,
                           views::MaximumFlexSizeRule::kPreferred, true))
          .Build());

  SetDesign(Design::kCurrent);
}

ResultView::~ResultView() = default;

void ResultView::SetFirstLineText(const std::u16string& first_line_text) {
  first_line_label_->SetText(first_line_text);
  first_line_label_->SetVisible(!first_line_text.empty());
}

std::u16string ResultView::GetFirstLineText() const {
  return first_line_label_->GetText();
}

void ResultView::SetFirstLineSubText(
    const std::u16string& first_line_sub_text) {
  first_line_sub_label_->SetText(first_line_sub_text);
  first_line_sub_label_->SetVisible(!first_line_sub_text.empty());
  separator_label_->SetVisible(!first_line_sub_text.empty());
  flex_layout_view_->SetFlexAllocationOrder(
      first_line_sub_text.empty() ? views::FlexAllocationOrder::kNormal
                                  : views::FlexAllocationOrder::kReverse);
}

std::u16string ResultView::GetFirstLineSubText() const {
  return first_line_sub_label_->GetText();
}

void ResultView::SetPhoneticsInfo(const PhoneticsInfo& phonetics_info) {
  phonetics_info_ = phonetics_info;
  phonetics_audio_button_->SetVisible(!IsEmpty(phonetics_info_));
}

PhoneticsInfo ResultView::GetPhoneticsInfo() const {
  return phonetics_info_;
}

void ResultView::SetSecondLineText(const std::u16string& second_line_text) {
  second_line_label_->SetText(second_line_text);
  second_line_label_->SetVisible(!second_line_text.empty());
}

std::u16string ResultView::GetSecondLineText() const {
  return second_line_label_->GetText();
}

void ResultView::SetGenerateTtsCallback(
    GenerateTtsCallback generate_tts_callback) {
  generate_tts_callback_ = generate_tts_callback;
}

std::u16string ResultView::GetA11yDescription() const {
  return l10n_util::GetStringFUTF16(
      IDS_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE_V2,
      first_line_label_->GetText(), second_line_label_->GetText());
}

void ResultView::SetDesign(Design design) {
  first_line_label_->SetFontList(GetFirstLineFontList(design));
  first_line_label_->SetLineHeight(GetFirstLineHeight(design));
  first_line_sub_label_->SetFontList(GetFirstLineFontList(design));
  first_line_sub_label_->SetLineHeight(GetFirstLineHeight(design));

  second_line_label_->SetFontList(GetSecondLineFontList(design));
  second_line_label_->SetLineHeight(GetSecondLineHeight(design));
}

void ResultView::OnPhoneticsAudioButtonPressed() {
  CHECK(!IsEmpty(phonetics_info_));
  CHECK(!generate_tts_callback_.is_null());
  generate_tts_callback_.Run(phonetics_info_);
}

BEGIN_METADATA(ResultView)
ADD_PROPERTY_METADATA(std::u16string, FirstLineText)
ADD_PROPERTY_METADATA(std::u16string, FirstLineSubText)
ADD_PROPERTY_METADATA(std::u16string, SecondLineText)
ADD_READONLY_PROPERTY_METADATA(std::u16string, A11yDescription)
END_METADATA

}  // namespace quick_answers