chromium/ash/system/mahi/mahi_error_status_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 "ash/system/mahi/mahi_error_status_view.h"

#include <memory>

#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/typography.h"
#include "ash/system/mahi/mahi_constants.h"
#include "ash/system/mahi/mahi_ui_update.h"
#include "ash/system/mahi/mahi_utils.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "chromeos/components/mahi/public/cpp/mahi_manager.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/view.h"

namespace ash {

namespace {

// Constants -------------------------------------------------------------------

constexpr auto kContentsPaddings =
    gfx::Insets::VH(/*vertical=*/40, /*horizontal=*/0);

constexpr auto kErrorStatusViewPaddings = gfx::Insets(/*all=*/8);

constexpr auto kImagePaddings = gfx::Insets::TLBR(/*top=*/0,
                                                  /*left=*/56,
                                                  /*bottom=*/16,
                                                  /*right=*/56);

constexpr gfx::Size kImagePreferredSize(/*width=*/200, /*height=*/100);

// TODO(b/319731776): Use panel bounds instead when the panel is resizable.
constexpr int kLabelMaximumWidth = 264;

constexpr auto kLabelPaddings =
    gfx::Insets::VH(/*vertical=*/0, /*horizontal=*/24);

// ErrorContentsView -----------------------------------------------------------

class ErrorContentsView : public views::FlexLayoutView,
                          public MahiUiController::Delegate {
 public:
  explicit ErrorContentsView(MahiUiController* ui_controller)
      : MahiUiController::Delegate(ui_controller),
        ui_controller_(ui_controller) {
    // TODO(http://b/319731862): Set the image when the image resource is ready.
    views::Builder<views::FlexLayoutView>(this)
        .SetBorder(views::CreateEmptyBorder(kContentsPaddings))
        .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
        .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
        .SetOrientation(views::LayoutOrientation::kVertical)
        .AddChildren(
            views::Builder<views::ImageView>()
                .SetBorder(views::CreateEmptyBorder(kImagePaddings))
                .SetImage(ui::ResourceBundle::GetSharedInstance()
                              .GetThemedLottieImageNamed(
                                  IDR_MAHI_GENERAL_ERROR_STATUS_IMAGE))
                .SetPreferredSize(kImagePreferredSize),
            views::Builder<views::Label>()
                .AfterBuild(base::BindOnce([](views::Label* self) {
                  TypographyProvider::Get()->StyleLabel(
                      TypographyToken::kCrosBody2, *self);
                }))
                .CopyAddressTo(&error_status_text_)
                .SetBorder(views::CreateEmptyBorder(kLabelPaddings))
                .SetEnabledColorId(cros_tokens::kCrosSysOnSurface)
                .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER)
                .SetID(mahi_constants::ViewId::kErrorStatusLabel)
                .SetMultiLine(true)
                .SetMaximumWidth(kLabelMaximumWidth),
            views::Builder<views::Link>()
                .CopyAddressTo(&retry_link_)
                .SetForceUnderline(false)
                .SetID(mahi_constants::ViewId::kErrorStatusRetryLink)
                .SetText(l10n_util::GetStringUTF16(
                    IDS_ASH_MAHI_RETRY_LINK_LABEL_TEXT))
                .SetVisible(false))
        .BuildChildren();
  }

  ErrorContentsView(const ErrorContentsView&) = delete;
  ErrorContentsView& operator=(const ErrorContentsView&) = delete;
  ~ErrorContentsView() override = default;

 private:
  // MahiUiController::Delegate:
  views::View* GetView() override { return this; }

  bool GetViewVisibility(VisibilityState state) const override {
    // Always visible because its parent view controls visibility.
    return true;
  }

  void OnUpdated(const MahiUiUpdate& update) override {
    switch (update.type()) {
      case MahiUiUpdateType::kErrorReceived: {
        const MahiUiError& error = update.GetError();
        error_status_text_->SetText(l10n_util::GetStringUTF16(
            mahi_utils::GetErrorStatusViewTextId(error.status)));

        retry_link_->SetVisible(
            mahi_utils::CalculateRetryLinkVisible(error.status));
        retry_link_->SetCallback(
            retry_link_->GetVisible()
                ? base::BindRepeating(
                      [](MahiUiController* controller,
                         VisibilityState origin_state,
                         views::View* retry_link) {
                        controller->Retry(origin_state);
                        retry_link->GetViewAccessibility().AnnounceText(
                            l10n_util::GetStringUTF16(
                                IDS_ASH_MAHI_RETRY_LINK_CLICK_ACTIVATION_ACCESSIBLE_NAME));
                      },
                      ui_controller_, error.origin_state, retry_link_)
                : base::RepeatingClosure());
        return;
      }
      case MahiUiUpdateType::kAnswerLoaded:
      case MahiUiUpdateType::kContentsRefreshInitiated:
      case MahiUiUpdateType::kOutlinesLoaded:
      case MahiUiUpdateType::kQuestionAndAnswerViewNavigated:
      case MahiUiUpdateType::kQuestionPosted:
      case MahiUiUpdateType::kQuestionReAsked:
      case MahiUiUpdateType::kRefreshAvailabilityUpdated:
      case MahiUiUpdateType::kSummaryLoaded:
      case MahiUiUpdateType::kSummaryAndOutlinesSectionNavigated:
      case MahiUiUpdateType::kSummaryAndOutlinesReloaded:
        return;
    }
  }

  const raw_ptr<MahiUiController> ui_controller_;

  raw_ptr<views::Label> error_status_text_ = nullptr;
  raw_ptr<views::Link> retry_link_ = nullptr;
};

}  // namespace

MahiErrorStatusView::MahiErrorStatusView(MahiUiController* ui_controller)
    : MahiUiController::Delegate(ui_controller) {
  CHECK(chromeos::features::IsMahiEnabled());

  views::Builder<views::FlexLayoutView>(this)
      .SetBorder(views::CreateEmptyBorder(kErrorStatusViewPaddings))
      .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
      .SetID(mahi_constants::ViewId::kErrorStatusView)
      .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
      .AddChild(views::Builder<views::View>(
          std::make_unique<ErrorContentsView>(ui_controller)))
      .BuildChildren();
}

MahiErrorStatusView::~MahiErrorStatusView() = default;

views::View* MahiErrorStatusView::GetView() {
  return this;
}

bool MahiErrorStatusView::GetViewVisibility(VisibilityState state) const {
  switch (state) {
    case VisibilityState::kError:
      return true;
    case VisibilityState::kQuestionAndAnswer:
    case VisibilityState::kSummaryAndOutlines:
      return false;
  }
}

BEGIN_METADATA(MahiErrorStatusView)
END_METADATA

}  // namespace ash