[High5] Implement header for the authentication dialog.
Bug: b:349265481, b:348326316
Change-Id: I358ba72353cc87f88768a3fb8dc74a7db1345dae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5650946
Reviewed-by: Hardik Goyal <hardikgoyal@chromium.org>
Reviewed-by: Renato Silva <rrsilva@google.com>
Commit-Queue: Istvan Nagy <iscsi@google.com>
Cr-Commit-Position: refs/heads/main@{#1320295}
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index e1bd660a..0a0f4027 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -336,6 +336,8 @@
"assistant/assistant_web_ui_controller.h",
"assistant/assistant_web_view_delegate_impl.cc",
"assistant/assistant_web_view_delegate_impl.h",
+ "auth/views/auth_header_view.cc",
+ "auth/views/auth_header_view.h",
"auth/views/auth_input_row_view.cc",
"auth/views/auth_input_row_view.h",
"auth/views/auth_textfield.cc",
@@ -4612,6 +4614,7 @@
"app_list/views/app_list_item_view_pixeltest.cc",
"app_list/views/app_list_view_pixeltest.cc",
"app_list/views/search_result_view_pixeltest.cc",
+ "auth/views/auth_header_view_pixeltest.cc",
"auth/views/auth_input_row_view_pixeltest.cc",
"auth/views/pin_keyboard_pixeltest.cc",
"capture_mode/capture_mode_pixeltest.cc",
diff --git a/ash/auth/views/auth_header_view.cc b/ash/auth/views/auth_header_view.cc
new file mode 100644
index 0000000..006b80f
--- /dev/null
+++ b/ash/auth/views/auth_header_view.cc
@@ -0,0 +1,134 @@
+// 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/auth/views/auth_header_view.h"
+
+#include "ash/ash_export.h"
+#include "ash/login/ui/animated_rounded_image_view.h"
+#include "ash/login/ui/non_accessible_view.h"
+#include "ash/public/cpp/login/login_utils.h"
+#include "ash/public/cpp/session/user_info.h"
+#include "ash/style/typography.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list_types.h"
+#include "components/account_id/account_id.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/color/color_id.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace {
+
+constexpr int kIconToTitleDistanceDp = 16;
+constexpr int kAvatarSizeDp = 40;
+
+constexpr ui::ColorId kTitleColorId = cros_tokens::kCrosSysOnSurface;
+constexpr ui::ColorId kTitleErrorColorId = cros_tokens::kCrosSysError;
+constexpr int kTitleLineWidthDp = 268;
+constexpr int kTitleToDescriptionDistanceDp = 8;
+constexpr TypographyToken kTitleFont = TypographyToken::kCrosTitle1;
+
+constexpr ui::ColorId kDescriptionColorId = cros_tokens::kCrosSysOnSurface;
+constexpr int kDescriptionLineWidthDp = 268;
+constexpr TypographyToken kDescriptionFont = TypographyToken::kCrosAnnotation1;
+
+} // namespace
+
+AuthHeaderView::AuthHeaderView(const AccountId& account_id,
+ const std::u16string& title,
+ const std::u16string& description)
+ : title_str_(title) {
+ auto decorate_label = [](views::Label* label) {
+ label->SetSubpixelRenderingEnabled(false);
+ label->SetAutoColorReadabilityEnabled(false);
+ label->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
+ };
+
+ auto add_spacer = [&](int height) {
+ auto* spacer = new NonAccessibleView();
+ spacer->SetPreferredSize(gfx::Size(0, height));
+ AddChildView(spacer);
+ };
+
+ auto layout = std::make_unique<views::BoxLayout>(
+ views::BoxLayout::Orientation::kVertical);
+ layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
+ layout->set_cross_axis_alignment(
+ views::BoxLayout::CrossAxisAlignment::kCenter);
+ SetLayoutManager(std::move(layout));
+
+ // Add avatar.
+ avatar_view_ = AddChildView(std::make_unique<AnimatedRoundedImageView>(
+ gfx::Size(kAvatarSizeDp, kAvatarSizeDp),
+ kAvatarSizeDp / 2 /*corner_radius*/));
+
+ const UserAvatar avatar = BuildAshUserAvatarForAccountId(account_id);
+ avatar_view_->SetImage(avatar.image);
+
+ // Add vertical space separator.
+ add_spacer(kIconToTitleDistanceDp);
+
+ // Add title.
+ title_label_ = new views::Label(title, views::style::CONTEXT_LABEL,
+ views::style::STYLE_PRIMARY);
+ title_label_->SetMultiLine(true);
+ title_label_->SizeToFit(kTitleLineWidthDp);
+ title_label_->SetEnabledColorId(kTitleColorId);
+ title_label_->SetFontList(
+ TypographyProvider::Get()->ResolveTypographyToken(kTitleFont));
+ decorate_label(title_label_);
+ AddChildView(title_label_.get());
+
+ // Add vertical space separator.
+ add_spacer(kTitleToDescriptionDistanceDp);
+
+ // Add description.
+ description_label_ = new views::Label(
+ description, views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY);
+ description_label_->SetMultiLine(true);
+ description_label_->SizeToFit(kDescriptionLineWidthDp);
+ description_label_->SetEnabledColorId(kDescriptionColorId);
+ description_label_->SetFontList(
+ TypographyProvider::Get()->ResolveTypographyToken(kDescriptionFont));
+ decorate_label(description_label_);
+ AddChildView(description_label_.get());
+}
+
+AuthHeaderView::~AuthHeaderView() {
+ avatar_view_ = nullptr;
+ title_label_ = nullptr;
+ description_label_ = nullptr;
+}
+
+gfx::Size AuthHeaderView::CalculatePreferredSize(
+ const views::SizeBounds& available_size) const {
+ const int preferred_height = kAvatarSizeDp + kIconToTitleDistanceDp +
+ title_label_->GetPreferredSize().height() +
+ kTitleToDescriptionDistanceDp +
+ description_label_->GetPreferredSize().height();
+ return gfx::Size(kTitleLineWidthDp, preferred_height);
+}
+
+void AuthHeaderView::SetErrorTitle(const std::u16string& error_str) {
+ title_label_->SetText(error_str);
+ title_label_->SetEnabledColorId(kTitleErrorColorId);
+}
+
+void AuthHeaderView::RestoreTitle() {
+ if (title_label_->GetText() != title_str_) {
+ title_label_->SetText(title_str_);
+ title_label_->SetEnabledColorId(kTitleColorId);
+ }
+}
+
+BEGIN_METADATA(AuthHeaderView)
+END_METADATA
+
+} // namespace ash
diff --git a/ash/auth/views/auth_header_view.h b/ash/auth/views/auth_header_view.h
new file mode 100644
index 0000000..0a34370
--- /dev/null
+++ b/ash/auth/views/auth_header_view.h
@@ -0,0 +1,61 @@
+// 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.
+
+#ifndef ASH_AUTH_VIEWS_AUTH_HEADER_VIEW_H_
+#define ASH_AUTH_VIEWS_AUTH_HEADER_VIEW_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "ash/login/ui/animated_rounded_image_view.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list_types.h"
+#include "components/account_id/account_id.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+// Contains the user avatar, title and description.
+// The title can display error message.
+// Provide function to restore the original title.
+class ASH_EXPORT AuthHeaderView : public views::View {
+ METADATA_HEADER(AuthHeaderView, views::View)
+
+ public:
+ AuthHeaderView(const AccountId& account_id,
+ const std::u16string& title,
+ const std::u16string& description);
+
+ AuthHeaderView(const AuthHeaderView&) = delete;
+ AuthHeaderView& operator=(const AuthHeaderView&) = delete;
+
+ ~AuthHeaderView() override;
+
+ // views::View:
+ gfx::Size CalculatePreferredSize(
+ const views::SizeBounds& available_size) const override;
+
+ base::WeakPtr<AuthHeaderView> AsWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ void SetErrorTitle(const std::u16string& error_str);
+ void RestoreTitle();
+
+ private:
+ raw_ptr<AnimatedRoundedImageView> avatar_view_ = nullptr;
+ raw_ptr<views::Label> title_label_ = nullptr;
+ raw_ptr<views::Label> description_label_ = nullptr;
+
+ const std::u16string title_str_;
+
+ base::WeakPtrFactory<AuthHeaderView> weak_ptr_factory_{this};
+};
+
+} // namespace ash
+
+#endif // ASH_AUTH_VIEWS_AUTH_HEADER_VIEW_H_
diff --git a/ash/auth/views/auth_header_view_pixeltest.cc b/ash/auth/views/auth_header_view_pixeltest.cc
new file mode 100644
index 0000000..baee1fe
--- /dev/null
+++ b/ash/auth/views/auth_header_view_pixeltest.cc
@@ -0,0 +1,103 @@
+// 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 <memory>
+#include <optional>
+#include <string>
+
+#include "ash/auth/views/auth_header_view.h"
+#include "ash/style/dark_light_mode_controller_impl.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/ash_test_util.h"
+#include "ash/test/pixel/ash_pixel_differ.h"
+#include "ash/test/pixel/ash_pixel_test_init_params.h"
+#include "components/user_manager/fake_user_manager.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+const std::string kUserEmail("user1@gmail.com");
+
+const std::u16string kTitle(u"Auth header view pixeltest title");
+const std::u16string kErrorTitle(u"Auth header view pixeltest error");
+
+const std::u16string kDescription(u"Auth header view pixeltest description");
+
+class AuthHeaderPixelTest : public AshTestBase {
+ public:
+ AuthHeaderPixelTest() = default;
+ AuthHeaderPixelTest(const AuthHeaderPixelTest&) = delete;
+ AuthHeaderPixelTest& operator=(const AuthHeaderPixelTest&) = delete;
+ ~AuthHeaderPixelTest() override = default;
+
+ protected:
+ std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
+ const override {
+ return pixel_test::InitParams();
+ }
+
+ // AshTestBase:
+ void SetUp() override {
+ AshTestBase::SetUp();
+ UpdateDisplay("600x800");
+
+ widget_ = CreateFramelessTestWidget();
+
+ AccountId account_id = AccountId::FromUserEmail(kUserEmail);
+ auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>();
+ fake_user_manager->AddUser(account_id);
+ scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
+ std::move(fake_user_manager));
+
+ std::unique_ptr<AuthHeaderView> header_view =
+ std::make_unique<AuthHeaderView>(account_id, kTitle, kDescription);
+
+ header_view->SetBackground(views::CreateThemedRoundedRectBackground(
+ cros_tokens::kCrosSysSystemBaseElevated, 0));
+
+ widget_->SetSize(header_view->GetPreferredSize());
+ widget_->Show();
+
+ header_view_ = widget_->SetContentsView(std::move(header_view));
+
+ auto* dark_light_mode_controller = DarkLightModeControllerImpl::Get();
+ dark_light_mode_controller->SetAutoScheduleEnabled(false);
+ // Test Base should setup the dark mode.
+ EXPECT_TRUE(dark_light_mode_controller->IsDarkModeEnabled());
+ }
+
+ void TearDown() override {
+ header_view_ = nullptr;
+ widget_.reset();
+ AshTestBase::TearDown();
+ }
+
+ std::unique_ptr<views::Widget> widget_;
+ raw_ptr<AuthHeaderView> header_view_ = nullptr;
+ std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
+};
+
+// Verify the header component look like in DayMode
+TEST_F(AuthHeaderPixelTest, DayMode) {
+ DarkLightModeControllerImpl::Get()->SetDarkModeEnabledForTest(false);
+ // Verify the UI.
+ EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+ "DayMode", /*revision_number=*/0, header_view_));
+ // Verify the error.
+ header_view_->SetErrorTitle(kErrorTitle);
+ EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+ "Error", /*revision_number=*/0, header_view_));
+ // Verify the restore
+ header_view_->RestoreTitle();
+ EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+ "Restore", /*revision_number=*/0, header_view_));
+}
+
+} // namespace
+} // namespace ash