0

Add unified GlanceablesListFooterView

This view is shared between classroom student, classroom teacher and
tasks bubbles. It displays "Showing X out of Y" test and "See all"
button.

Low-Coverage-Reason: Added *::OnSeeAllPressed() are NOTIMPLEMENTED().

Bug: b/283370862
Change-Id: I1096fed1b9cf781122bfd5be71c02ab801b464b6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4662135
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Commit-Queue: Artsiom Mitrokhin <amitrokhin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1166548}
This commit is contained in:
Artsiom Mitrokhin
2023-07-06 15:22:46 +00:00
committed by Chromium LUCI CQ
parent f0fca50a5e
commit 0adbb3cb1a
19 changed files with 287 additions and 137 deletions

@@ -618,6 +618,9 @@ component("ash") {
"glanceables/classroom/glanceables_classroom_item_view.h", "glanceables/classroom/glanceables_classroom_item_view.h",
"glanceables/classroom/glanceables_classroom_types.cc", "glanceables/classroom/glanceables_classroom_types.cc",
"glanceables/classroom/glanceables_classroom_types.h", "glanceables/classroom/glanceables_classroom_types.h",
"glanceables/common/glanceables_list_footer_view.cc",
"glanceables/common/glanceables_list_footer_view.h",
"glanceables/common/glanceables_view_id.h",
"glanceables/glanceables_controller.cc", "glanceables/glanceables_controller.cc",
"glanceables/glanceables_controller.h", "glanceables/glanceables_controller.h",
"glanceables/glanceables_delegate.h", "glanceables/glanceables_delegate.h",

@@ -6717,10 +6717,10 @@ New install
<message name="IDS_GLANCEABLES_WELCOME_LABEL" desc="Personalized greeting / welcome message shown on glanceables surfaces (welcome screen and overview mode)."> <message name="IDS_GLANCEABLES_WELCOME_LABEL" desc="Personalized greeting / welcome message shown on glanceables surfaces (welcome screen and overview mode).">
Welcome back, <ph name="GIVEN_NAME">$1<ex>John</ex></ph> Welcome back, <ph name="GIVEN_NAME">$1<ex>John</ex></ph>
</message> </message>
<message name="IDS_GLANCEABLES_TASKS_DETAILS_FOOTER" desc="The glanceable displays tasks from a task list that's selected in a drop-down box within the glanceable UI. The first placeholder value is the number of tasks visible in the UI. The second placeholder value is the total number of tasks in the selected tasks list, including tasks that are not visible in the UI."> <message name="IDS_GLANCEABLES_LIST_FOOTER_ITEMS_COUNT_LABEL" desc="The glanceable displays classroom or tasks items fetched from Google Classroom or Google Tasks API. The first placeholder value is the number of items visible in the UI. The second placeholder value is the total number of items returned from API.">
Showing <ph name="NUM_SHOWN_TASKS">$1<ex>5</ex></ph> out of <ph name="NUM_TOTAL_TASKS">$2<ex>10</ex></ph> Showing <ph name="NUM_SHOWN_ITEMS">$1<ex>5</ex></ph> out of <ph name="NUM_TOTAL_ITEMS">$2<ex>10</ex></ph>
</message> </message>
<message name="IDS_GLANCEABLES_TASKS_ACTION_BUTTON_LABEL" desc="The glanceable displays tasks from a task list that's selected in a drop-down box within the glanceable UI. Interacting with the button and label at the bottom of the glanceable opens the Tasks companion app."> <message name="IDS_GLANCEABLES_LIST_FOOTER_ACTION_BUTTON_LABEL" desc="The glanceable displays classroom or tasks items fetched from Google Classroom or Google Tasks API. Glanceables UI renders only limited number of items, the rest of them can be seen by clicking this button, which will open a corresponding website.">
See all See all
</message> </message>
<message name="IDS_GLANCEABLES_CLASSROOM_ASSIGNMENT_DUE_TODAY" desc="Classroom glanceable displays upcoming/missed/completed assignments from Google Classroom. This text is displayed for assignments that are due today. In other cases date formatter is used and no translation is needed."> <message name="IDS_GLANCEABLES_CLASSROOM_ASSIGNMENT_DUE_TODAY" desc="Classroom glanceable displays upcoming/missed/completed assignments from Google Classroom. This text is displayed for assignments that are due today. In other cases date formatter is used and no translation is needed.">

@@ -8,6 +8,7 @@
#include "ash/glanceables/classroom/glanceables_classroom_client.h" #include "ash/glanceables/classroom/glanceables_classroom_client.h"
#include "ash/glanceables/classroom/glanceables_classroom_types.h" #include "ash/glanceables/classroom/glanceables_classroom_types.h"
#include "ash/glanceables/common/glanceables_view_id.h"
#include "ash/resources/vector_icons/vector_icons.h" #include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h" #include "ash/strings/grit/ash_strings.h"
@@ -20,6 +21,7 @@
#include "base/i18n/time_formatting.h" #include "base/i18n/time_formatting.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/types/cxx23_to_underlying.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h" #include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h" #include "ui/base/models/image_model.h"
@@ -99,7 +101,7 @@ std::unique_ptr<views::ImageView> BuildIcon() {
return views::Builder<views::ImageView>() return views::Builder<views::ImageView>()
.SetBackground(views::CreateThemedRoundedRectBackground( .SetBackground(views::CreateThemedRoundedRectBackground(
cros_tokens::kCrosSysSystemOnBase1, kIconViewBackgroundRadius)) cros_tokens::kCrosSysSystemOnBase1, kIconViewBackgroundRadius))
.SetID(GlanceablesClassroomItemView::kIconViewId) .SetID(base::to_underlying(GlanceablesViewId::kClassroomItemIcon))
.SetImage(ui::ImageModel::FromVectorIcon( .SetImage(ui::ImageModel::FromVectorIcon(
kGlanceablesClassroomAssignmentIcon, cros_tokens::kCrosSysOnSurface, kGlanceablesClassroomAssignmentIcon, cros_tokens::kCrosSysOnSurface,
kIconSize)) kIconSize))
@@ -120,18 +122,19 @@ std::unique_ptr<views::BoxLayoutView> BuildAssignmentTitleLabels(
views::kFlexBehaviorKey, views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded)) views::MaximumFlexSizeRule::kUnbounded))
.AddChild( .AddChild(views::Builder<views::Label>()
views::Builder<views::Label>() .SetText(base::UTF8ToUTF16(assignment->course_work_title))
.SetText(base::UTF8ToUTF16(assignment->course_work_title)) .SetID(base::to_underlying(
.SetID(GlanceablesClassroomItemView::kCourseWorkTitleLabelId) GlanceablesViewId::kClassroomItemCourseWorkTitleLabel))
.SetEnabledColorId(cros_tokens::kCrosSysOnSurface) .SetEnabledColorId(cros_tokens::kCrosSysOnSurface)
.SetFontList(typography_provider->ResolveTypographyToken( .SetFontList(typography_provider->ResolveTypographyToken(
TypographyToken::kCrosButton2)) TypographyToken::kCrosButton2))
.SetLineHeight(typography_provider->ResolveLineHeight( .SetLineHeight(typography_provider->ResolveLineHeight(
TypographyToken::kCrosButton2))) TypographyToken::kCrosButton2)))
.AddChild(views::Builder<views::Label>() .AddChild(views::Builder<views::Label>()
.SetText(base::UTF8ToUTF16(assignment->course_title)) .SetText(base::UTF8ToUTF16(assignment->course_title))
.SetID(GlanceablesClassroomItemView::kCourseTitleLabelId) .SetID(base::to_underlying(
GlanceablesViewId::kClassroomItemCourseTitleLabel))
.SetEnabledColorId(cros_tokens::kCrosSysOnSurfaceVariant) .SetEnabledColorId(cros_tokens::kCrosSysOnSurfaceVariant)
.SetFontList(typography_provider->ResolveTypographyToken( .SetFontList(typography_provider->ResolveTypographyToken(
TypographyToken::kCrosAnnotation1)) TypographyToken::kCrosAnnotation1))
@@ -151,7 +154,8 @@ std::unique_ptr<views::BoxLayoutView> BuildDueLabels(
.SetProperty(views::kMarginsKey, kDueLabelsMargin) .SetProperty(views::kMarginsKey, kDueLabelsMargin)
.AddChild(views::Builder<views::Label>() .AddChild(views::Builder<views::Label>()
.SetText(GetFormattedDueDate(assignment->due.value())) .SetText(GetFormattedDueDate(assignment->due.value()))
.SetID(GlanceablesClassroomItemView::kDueDateLabelId) .SetID(base::to_underlying(
GlanceablesViewId::kClassroomItemDueDateLabel))
.SetEnabledColorId(cros_tokens::kCrosSysOnSurfaceVariant) .SetEnabledColorId(cros_tokens::kCrosSysOnSurfaceVariant)
.SetFontList(typography_provider->ResolveTypographyToken( .SetFontList(typography_provider->ResolveTypographyToken(
TypographyToken::kCrosAnnotation1)) TypographyToken::kCrosAnnotation1))
@@ -159,7 +163,8 @@ std::unique_ptr<views::BoxLayoutView> BuildDueLabels(
TypographyToken::kCrosAnnotation1))) TypographyToken::kCrosAnnotation1)))
.AddChild(views::Builder<views::Label>() .AddChild(views::Builder<views::Label>()
.SetText(GetFormattedDueTime(assignment->due.value())) .SetText(GetFormattedDueTime(assignment->due.value()))
.SetID(GlanceablesClassroomItemView::kDueTimeLabelId) .SetID(base::to_underlying(
GlanceablesViewId::kClassroomItemDueTimeLabel))
.SetEnabledColorId(cros_tokens::kCrosSysOnSurfaceVariant) .SetEnabledColorId(cros_tokens::kCrosSysOnSurfaceVariant)
.SetFontList(typography_provider->ResolveTypographyToken( .SetFontList(typography_provider->ResolveTypographyToken(
TypographyToken::kCrosAnnotation1)) TypographyToken::kCrosAnnotation1))

@@ -19,13 +19,6 @@ class ASH_EXPORT GlanceablesClassroomItemView : public views::FlexLayoutView {
public: public:
METADATA_HEADER(GlanceablesClassroomItemView); METADATA_HEADER(GlanceablesClassroomItemView);
// Known view ids.
static constexpr int kIconViewId = 1;
static constexpr int kCourseWorkTitleLabelId = 2;
static constexpr int kCourseTitleLabelId = 3;
static constexpr int kDueDateLabelId = 4;
static constexpr int kDueTimeLabelId = 5;
explicit GlanceablesClassroomItemView( explicit GlanceablesClassroomItemView(
const GlanceablesClassroomStudentAssignment* assignment); const GlanceablesClassroomStudentAssignment* assignment);
GlanceablesClassroomItemView(const GlanceablesClassroomItemView&) = delete; GlanceablesClassroomItemView(const GlanceablesClassroomItemView&) = delete;

@@ -8,12 +8,14 @@
#include <vector> #include <vector>
#include "ash/glanceables/classroom/glanceables_classroom_types.h" #include "ash/glanceables/classroom/glanceables_classroom_types.h"
#include "ash/glanceables/common/glanceables_view_id.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/system/model/system_tray_model.h" #include "ash/system/model/system_tray_model.h"
#include "ash/system/time/calendar_unittest_utils.h" #include "ash/system/time/calendar_unittest_utils.h"
#include "ash/test/ash_test_base.h" #include "ash/test/ash_test_base.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/time/time_override.h" #include "base/time/time_override.h"
#include "base/types/cxx23_to_underlying.h"
#include "chromeos/ash/components/settings/scoped_timezone_settings.h" #include "chromeos/ash/components/settings/scoped_timezone_settings.h"
#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/views/controls/image_view.h" #include "ui/views/controls/image_view.h"
@@ -27,32 +29,34 @@ class GlanceablesClassroomItemViewTest : public AshTestBase {
public: public:
const views::ImageView* GetIconView( const views::ImageView* GetIconView(
const GlanceablesClassroomItemView& view) const { const GlanceablesClassroomItemView& view) const {
return views::AsViewClass<views::ImageView>( return views::AsViewClass<views::ImageView>(view.GetViewByID(
view.GetViewByID(GlanceablesClassroomItemView::kIconViewId)); base::to_underlying(GlanceablesViewId::kClassroomItemIcon)));
} }
const views::Label* GetCourseWorkTitleLabel( const views::Label* GetCourseWorkTitleLabel(
const GlanceablesClassroomItemView& view) const { const GlanceablesClassroomItemView& view) const {
return views::AsViewClass<views::Label>(view.GetViewByID( return views::AsViewClass<views::Label>(
GlanceablesClassroomItemView::kCourseWorkTitleLabelId)); view.GetViewByID(base::to_underlying(
GlanceablesViewId::kClassroomItemCourseWorkTitleLabel)));
} }
const views::Label* GetCourseTitleLabel( const views::Label* GetCourseTitleLabel(
const GlanceablesClassroomItemView& view) const { const GlanceablesClassroomItemView& view) const {
return views::AsViewClass<views::Label>( return views::AsViewClass<views::Label>(
view.GetViewByID(GlanceablesClassroomItemView::kCourseTitleLabelId)); view.GetViewByID(base::to_underlying(
GlanceablesViewId::kClassroomItemCourseTitleLabel)));
} }
const views::Label* GetDueDateLabel( const views::Label* GetDueDateLabel(
const GlanceablesClassroomItemView& view) const { const GlanceablesClassroomItemView& view) const {
return views::AsViewClass<views::Label>( return views::AsViewClass<views::Label>(view.GetViewByID(
view.GetViewByID(GlanceablesClassroomItemView::kDueDateLabelId)); base::to_underlying(GlanceablesViewId::kClassroomItemDueDateLabel)));
} }
const views::Label* GetDueTimeLabel( const views::Label* GetDueTimeLabel(
const GlanceablesClassroomItemView& view) const { const GlanceablesClassroomItemView& view) const {
return views::AsViewClass<views::Label>( return views::AsViewClass<views::Label>(view.GetViewByID(
view.GetViewByID(GlanceablesClassroomItemView::kDueTimeLabelId)); base::to_underlying(GlanceablesViewId::kClassroomItemDueTimeLabel)));
} }
}; };

@@ -0,0 +1,91 @@
// Copyright 2023 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/glanceables/common/glanceables_list_footer_view.h"
#include <algorithm>
#include <memory>
#include "ash/glanceables/common/glanceables_view_id.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/typography.h"
#include "base/check_op.h"
#include "base/functional/callback_forward.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/cxx23_to_underlying.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/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/metadata/view_factory_internal.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
namespace ash {
namespace {
constexpr int kSeeAllIconSize = 24;
} // namespace
GlanceablesListFooterView::GlanceablesListFooterView(
base::RepeatingClosure on_see_all_pressed) {
SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
const auto* const typography_provider = TypographyProvider::Get();
items_count_label_ = AddChildView(
views::Builder<views::Label>()
.SetID(base::to_underlying(
GlanceablesViewId::kListFooterItemsCountLabel))
.SetEnabledColorId(cros_tokens::kCrosSysSecondary)
.SetFontList(typography_provider->ResolveTypographyToken(
TypographyToken::kCrosBody2))
.SetLineHeight(typography_provider->ResolveLineHeight(
TypographyToken::kCrosBody2))
.SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
.SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded))
.Build());
auto* const see_all_button =
AddChildView(views::Builder<views::LabelButton>()
.SetText(l10n_util::GetStringUTF16(
IDS_GLANCEABLES_LIST_FOOTER_ACTION_BUTTON_LABEL))
.SetCallback(std::move(on_see_all_pressed))
.SetID(base::to_underlying(
GlanceablesViewId::kListFooterSeeAllButton))
.Build());
see_all_button->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_RIGHT);
see_all_button->SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(vector_icons::kLaunchIcon,
cros_tokens::kCrosSysOnSurface,
kSeeAllIconSize));
see_all_button->SetTextColorId(views::Button::STATE_NORMAL,
cros_tokens::kCrosSysOnSurface);
}
void GlanceablesListFooterView::UpdateItemsCount(size_t visible_items_count,
size_t total_items_count) {
CHECK_LE(visible_items_count, total_items_count);
items_count_label_->SetText(
l10n_util::GetStringFUTF16(IDS_GLANCEABLES_LIST_FOOTER_ITEMS_COUNT_LABEL,
base::NumberToString16(visible_items_count),
base::NumberToString16(total_items_count)));
}
BEGIN_METADATA(GlanceablesListFooterView, views::View)
END_METADATA
} // namespace ash

@@ -0,0 +1,43 @@
// Copyright 2023 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_GLANCEABLES_COMMON_GLANCEABLES_LIST_FOOTER_VIEW_H_
#define ASH_GLANCEABLES_COMMON_GLANCEABLES_LIST_FOOTER_VIEW_H_
#include "ash/ash_export.h"
#include "base/allocator/partition_allocator/pointers/raw_ptr.h"
#include "base/functional/callback_forward.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/layout/flex_layout_view.h"
namespace views {
class Label;
} // namespace views
namespace ash {
// Renders "Showing X out of Y" label and "See all" button. Used in classroom
// and tasks bubbles.
class ASH_EXPORT GlanceablesListFooterView : public views::FlexLayoutView {
public:
METADATA_HEADER(GlanceablesListFooterView);
explicit GlanceablesListFooterView(base::RepeatingClosure on_see_all_pressed);
GlanceablesListFooterView(const GlanceablesListFooterView&) = delete;
GlanceablesListFooterView& operator=(const GlanceablesListFooterView&) =
delete;
~GlanceablesListFooterView() override = default;
// Updates `items_count_label_`.
// `visible_items_count` - number of items visible/rendered in a list.
// `total_items_count` - total number of items returned from API.
void UpdateItemsCount(size_t visible_items_count, size_t total_items_count);
private:
raw_ptr<views::Label, ExperimentalAsh> items_count_label_ = nullptr;
};
} // namespace ash
#endif // ASH_GLANCEABLES_COMMON_GLANCEABLES_LIST_FOOTER_VIEW_H_

@@ -0,0 +1,33 @@
// Copyright 2023 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_GLANCEABLES_COMMON_GLANCEABLES_VIEW_ID_H_
#define ASH_GLANCEABLES_COMMON_GLANCEABLES_VIEW_ID_H_
namespace ash {
// Known view ids assigned to glanceables views to query them in tests.
enum class GlanceablesViewId {
kDefaultIdZero,
// `GlanceablesListFooterView`.
kListFooterItemsCountLabel,
kListFooterSeeAllButton,
// `ClassroomBubbleBaseView`.
kClassroomBubbleComboBox,
kClassroomBubbleListContainer,
kClassroomBubbleListFooter,
// `GlanceablesClassroomItemView`.
kClassroomItemIcon,
kClassroomItemCourseWorkTitleLabel,
kClassroomItemCourseTitleLabel,
kClassroomItemDueDateLabel,
kClassroomItemDueTimeLabel,
};
} // namespace ash
#endif // ASH_GLANCEABLES_COMMON_GLANCEABLES_VIEW_ID_H_

@@ -6,7 +6,11 @@
#include <memory> #include <memory>
#include "ash/glanceables/common/glanceables_list_footer_view.h"
#include "ash/glanceables/common/glanceables_view_id.h"
#include "ash/resources/vector_icons/vector_icons.h" #include "ash/resources/vector_icons/vector_icons.h"
#include "base/functional/bind.h"
#include "base/types/cxx23_to_underlying.h"
#include "ui/base/metadata/metadata_impl_macros.h" #include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/combobox_model.h" #include "ui/base/models/combobox_model.h"
#include "ui/base/models/image_model.h" #include "ui/base/models/image_model.h"
@@ -51,14 +55,16 @@ ClassroomBubbleBaseView::ClassroomBubbleBaseView(
combo_box_view_ = header_view_->AddChildView( combo_box_view_ = header_view_->AddChildView(
std::make_unique<views::Combobox>(std::move(combobox_model))); std::make_unique<views::Combobox>(std::move(combobox_model)));
combo_box_view_->SetID(kComboBoxViewId); combo_box_view_->SetID(
base::to_underlying(GlanceablesViewId::kClassroomBubbleComboBox));
combo_box_view_->SetSelectedIndex(0); combo_box_view_->SetSelectedIndex(0);
// TODO(b:283370907): Implement accessibility behavior. // TODO(b:283370907): Implement accessibility behavior.
combo_box_view_->SetTooltipTextAndAccessibleName(u"Assignment list selector"); combo_box_view_->SetTooltipTextAndAccessibleName(u"Assignment list selector");
list_container_view_ = list_container_view_ =
AddChildView(std::make_unique<views::FlexLayoutView>()); AddChildView(std::make_unique<views::FlexLayoutView>());
list_container_view_->SetID(kListContainerViewId); list_container_view_->SetID(
base::to_underlying(GlanceablesViewId::kClassroomBubbleListContainer));
list_container_view_->SetOrientation(views::LayoutOrientation::kVertical); list_container_view_->SetOrientation(views::LayoutOrientation::kVertical);
list_container_view_->SetPaintToLayer(); list_container_view_->SetPaintToLayer();
list_container_view_->layer()->SetFillsBoundsOpaquely(false); list_container_view_->layer()->SetFillsBoundsOpaquely(false);
@@ -67,6 +73,12 @@ ClassroomBubbleBaseView::ClassroomBubbleBaseView(
list_container_view_->SetProperty( list_container_view_->SetProperty(
views::kMarginsKey, views::kMarginsKey,
gfx::Insets::TLBR(kSpacingAboveListContainerView, 0, 0, 0)); gfx::Insets::TLBR(kSpacingAboveListContainerView, 0, 0, 0));
list_footer_view_ = AddChildView(
std::make_unique<GlanceablesListFooterView>(base::BindRepeating(
&ClassroomBubbleBaseView::OnSeeAllPressed, base::Unretained(this))));
list_footer_view_->SetID(
base::to_underlying(GlanceablesViewId::kClassroomBubbleListFooter));
} }
ClassroomBubbleBaseView::~ClassroomBubbleBaseView() = default; ClassroomBubbleBaseView::~ClassroomBubbleBaseView() = default;

@@ -19,31 +19,34 @@ class ComboboxModel;
namespace ash { namespace ash {
class GlanceablesListFooterView;
class ASH_EXPORT ClassroomBubbleBaseView : public GlanceableTrayChildBubble { class ASH_EXPORT ClassroomBubbleBaseView : public GlanceableTrayChildBubble {
public: public:
METADATA_HEADER(ClassroomBubbleBaseView); METADATA_HEADER(ClassroomBubbleBaseView);
// Known view ids.
static constexpr int kComboBoxViewId = 1;
static constexpr int kListContainerViewId = 2;
// TODO(b:283370907): Add classroom glanceable contents. // TODO(b:283370907): Add classroom glanceable contents.
explicit ClassroomBubbleBaseView( ClassroomBubbleBaseView(DetailedViewDelegate* delegate,
DetailedViewDelegate* delegate, std::unique_ptr<ui::ComboboxModel> combobox_model);
std::unique_ptr<ui::ComboboxModel> combobox_model);
ClassroomBubbleBaseView(const ClassroomBubbleBaseView&) = delete; ClassroomBubbleBaseView(const ClassroomBubbleBaseView&) = delete;
ClassroomBubbleBaseView& operator-(const ClassroomBubbleBaseView&) = delete; ClassroomBubbleBaseView& operator=(const ClassroomBubbleBaseView&) = delete;
~ClassroomBubbleBaseView() override; ~ClassroomBubbleBaseView() override;
// views::View: // views::View:
void GetAccessibleNodeData(ui::AXNodeData* node_data) override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
protected: protected:
// Handles press on the "See all" button in `GlanceablesListFooterView`. Opens
// classroom web UI based on the selected menu option.
virtual void OnSeeAllPressed() = 0;
// Owned by views hierarchy. // Owned by views hierarchy.
raw_ptr<views::FlexLayoutView, ExperimentalAsh> header_view_ = nullptr; raw_ptr<views::FlexLayoutView, ExperimentalAsh> header_view_ = nullptr;
raw_ptr<views::Combobox, ExperimentalAsh> combo_box_view_ = nullptr; raw_ptr<views::Combobox, ExperimentalAsh> combo_box_view_ = nullptr;
raw_ptr<views::FlexLayoutView, ExperimentalAsh> list_container_view_ = raw_ptr<views::FlexLayoutView, ExperimentalAsh> list_container_view_ =
nullptr; nullptr;
raw_ptr<GlanceablesListFooterView, ExperimentalAsh> list_footer_view_ =
nullptr;
base::WeakPtrFactory<ClassroomBubbleBaseView> weak_ptr_factory_{this}; base::WeakPtrFactory<ClassroomBubbleBaseView> weak_ptr_factory_{this};
}; };

@@ -11,11 +11,13 @@
#include "ash/glanceables/classroom/glanceables_classroom_client.h" #include "ash/glanceables/classroom/glanceables_classroom_client.h"
#include "ash/glanceables/classroom/glanceables_classroom_item_view.h" #include "ash/glanceables/classroom/glanceables_classroom_item_view.h"
#include "ash/glanceables/classroom/glanceables_classroom_types.h" #include "ash/glanceables/classroom/glanceables_classroom_types.h"
#include "ash/glanceables/common/glanceables_list_footer_view.h"
#include "ash/glanceables/glanceables_v2_controller.h" #include "ash/glanceables/glanceables_v2_controller.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/system/tray/detailed_view_delegate.h" #include "ash/system/tray/detailed_view_delegate.h"
#include "base/check.h" #include "base/check.h"
#include "base/functional/bind.h" #include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/strings/string_piece_forward.h" #include "base/strings/string_piece_forward.h"
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
#include "ui/views/controls/combobox/combobox.h" #include "ui/views/controls/combobox/combobox.h"
@@ -91,6 +93,10 @@ ClassroomBubbleStudentView::ClassroomBubbleStudentView(
ClassroomBubbleStudentView::~ClassroomBubbleStudentView() = default; ClassroomBubbleStudentView::~ClassroomBubbleStudentView() = default;
void ClassroomBubbleStudentView::OnSeeAllPressed() {
NOTIMPLEMENTED();
}
void ClassroomBubbleStudentView::OnGetStudentAssignments( void ClassroomBubbleStudentView::OnGetStudentAssignments(
std::vector<std::unique_ptr<GlanceablesClassroomStudentAssignment>> std::vector<std::unique_ptr<GlanceablesClassroomStudentAssignment>>
assignments) { assignments) {
@@ -112,6 +118,9 @@ void ClassroomBubbleStudentView::OnGetStudentAssignments(
list_container_view_->children().back()->SetProperty(views::kMarginsKey, list_container_view_->children().back()->SetProperty(views::kMarginsKey,
gfx::Insets()); gfx::Insets());
} }
list_footer_view_->UpdateItemsCount(list_container_view_->children().size(),
assignments.size());
} }
void ClassroomBubbleStudentView::SelectedAssignmentListChanged() { void ClassroomBubbleStudentView::SelectedAssignmentListChanged() {

@@ -20,6 +20,10 @@ class ASH_EXPORT ClassroomBubbleStudentView : public ClassroomBubbleBaseView {
delete; delete;
~ClassroomBubbleStudentView() override; ~ClassroomBubbleStudentView() override;
private:
// ClassroomBubbleBaseView:
void OnSeeAllPressed() override;
// Handle switching between assignment lists. // Handle switching between assignment lists.
void SelectedAssignmentListChanged(); void SelectedAssignmentListChanged();

@@ -11,11 +11,13 @@
#include "ash/glanceables/classroom/glanceables_classroom_client.h" #include "ash/glanceables/classroom/glanceables_classroom_client.h"
#include "ash/glanceables/classroom/glanceables_classroom_item_view.h" #include "ash/glanceables/classroom/glanceables_classroom_item_view.h"
#include "ash/glanceables/classroom/glanceables_classroom_types.h" #include "ash/glanceables/classroom/glanceables_classroom_types.h"
#include "ash/glanceables/common/glanceables_list_footer_view.h"
#include "ash/glanceables/glanceables_v2_controller.h" #include "ash/glanceables/glanceables_v2_controller.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/system/tray/detailed_view_delegate.h" #include "ash/system/tray/detailed_view_delegate.h"
#include "base/check.h" #include "base/check.h"
#include "base/functional/bind.h" #include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/strings/string_piece_forward.h" #include "base/strings/string_piece_forward.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
@@ -92,6 +94,10 @@ ClassroomBubbleTeacherView::ClassroomBubbleTeacherView(
ClassroomBubbleTeacherView::~ClassroomBubbleTeacherView() = default; ClassroomBubbleTeacherView::~ClassroomBubbleTeacherView() = default;
void ClassroomBubbleTeacherView::OnSeeAllPressed() {
NOTIMPLEMENTED();
}
void ClassroomBubbleTeacherView::OnGetTeacherAssignments( void ClassroomBubbleTeacherView::OnGetTeacherAssignments(
std::vector<std::unique_ptr<GlanceablesClassroomTeacherAssignment>> std::vector<std::unique_ptr<GlanceablesClassroomTeacherAssignment>>
assignments) { assignments) {
@@ -113,6 +119,9 @@ void ClassroomBubbleTeacherView::OnGetTeacherAssignments(
list_container_view_->children().back()->SetProperty(views::kMarginsKey, list_container_view_->children().back()->SetProperty(views::kMarginsKey,
gfx::Insets()); gfx::Insets());
} }
list_footer_view_->UpdateItemsCount(list_container_view_->children().size(),
assignments.size());
} }
void ClassroomBubbleTeacherView::SelectedAssignmentListChanged() { void ClassroomBubbleTeacherView::SelectedAssignmentListChanged() {

@@ -19,6 +19,10 @@ class ASH_EXPORT ClassroomBubbleTeacherView : public ClassroomBubbleBaseView {
delete; delete;
~ClassroomBubbleTeacherView() override; ~ClassroomBubbleTeacherView() override;
private:
// ClassroomBubbleBaseView:
void OnSeeAllPressed() override;
// Handle switching between assignment lists. // Handle switching between assignment lists.
void SelectedAssignmentListChanged(); void SelectedAssignmentListChanged();

@@ -11,6 +11,8 @@
#include "ash/constants/ash_features.h" #include "ash/constants/ash_features.h"
#include "ash/glanceables/classroom/glanceables_classroom_client.h" #include "ash/glanceables/classroom/glanceables_classroom_client.h"
#include "ash/glanceables/classroom/glanceables_classroom_types.h" #include "ash/glanceables/classroom/glanceables_classroom_types.h"
#include "ash/glanceables/common/glanceables_list_footer_view.h"
#include "ash/glanceables/common/glanceables_view_id.h"
#include "ash/glanceables/glanceables_v2_controller.h" #include "ash/glanceables/glanceables_v2_controller.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/system/tray/detailed_view_delegate.h" #include "ash/system/tray/detailed_view_delegate.h"
@@ -19,11 +21,13 @@
#include "ash/test/ash_test_base.h" #include "ash/test/ash_test_base.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/types/cxx23_to_underlying.h"
#include "components/account_id/account_id.h" #include "components/account_id/account_id.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/views/controls/combobox/combobox.h" #include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/view_utils.h" #include "ui/views/view_utils.h"
#include "url/gurl.h" #include "url/gurl.h"
@@ -94,13 +98,18 @@ class ClassroomBubbleViewTest : public AshTestBase {
} }
views::Combobox* GetComboBoxView() { views::Combobox* GetComboBoxView() {
return views::AsViewClass<views::Combobox>( return views::AsViewClass<views::Combobox>(view_->GetViewByID(
view_->GetViewByID(ClassroomBubbleBaseView::kComboBoxViewId)); base::to_underlying(GlanceablesViewId::kClassroomBubbleComboBox)));
} }
const views::View* GetListContainerView() const { const views::View* GetListContainerView() const {
return views::AsViewClass<views::View>( return views::AsViewClass<views::View>(view_->GetViewByID(
view_->GetViewByID(ClassroomBubbleBaseView::kListContainerViewId)); base::to_underlying(GlanceablesViewId::kClassroomBubbleListContainer)));
}
const views::Label* GetListFooterItemsCountLabel() const {
return views::AsViewClass<views::Label>(view_->GetViewByID(
base::to_underlying(GlanceablesViewId::kListFooterItemsCountLabel)));
} }
protected: protected:
@@ -245,6 +254,9 @@ TEST_F(ClassroomBubbleStudentViewTest, RendersListItems) {
GetComboBoxView()->MenuSelectionAt(3); GetComboBoxView()->MenuSelectionAt(3);
EXPECT_EQ(GetListContainerView()->children().size(), 3u); // No more than 3. EXPECT_EQ(GetListContainerView()->children().size(), 3u); // No more than 3.
ASSERT_TRUE(GetListFooterItemsCountLabel());
EXPECT_EQ(GetListFooterItemsCountLabel()->GetText(), u"Showing 3 out of 5");
} }
TEST_F(ClassroomBubbleTeacherViewTest, RendersListItems) { TEST_F(ClassroomBubbleTeacherViewTest, RendersListItems) {
@@ -269,6 +281,9 @@ TEST_F(ClassroomBubbleTeacherViewTest, RendersListItems) {
GetComboBoxView()->MenuSelectionAt(3); GetComboBoxView()->MenuSelectionAt(3);
EXPECT_EQ(GetListContainerView()->children().size(), 3u); // No more than 3. EXPECT_EQ(GetListContainerView()->children().size(), 3u); // No more than 3.
ASSERT_TRUE(GetListFooterItemsCountLabel());
EXPECT_EQ(GetListFooterItemsCountLabel()->GetText(), u"Showing 3 out of 5");
} }
} // namespace ash } // namespace ash

@@ -5,26 +5,21 @@
#include "ash/system/unified/tasks_bubble_view.h" #include "ash/system/unified/tasks_bubble_view.h"
#include <algorithm> #include <algorithm>
#include <memory>
#include "ash/glanceables/common/glanceables_list_footer_view.h"
#include "ash/glanceables/glanceables_v2_controller.h" #include "ash/glanceables/glanceables_v2_controller.h"
#include "ash/glanceables/tasks/glanceables_task_view.h"
#include "ash/glanceables/tasks/glanceables_tasks_client.h" #include "ash/glanceables/tasks/glanceables_tasks_client.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/icon_button.h"
#include "ash/system/tray/tray_constants.h" #include "ash/system/tray/tray_constants.h"
#include "ash/system/unified/glanceable_tray_child_bubble.h" #include "ash/system/unified/glanceable_tray_child_bubble.h"
#include "ash/system/unified/tasks_combobox_model.h" #include "ash/system/unified/tasks_combobox_model.h"
#include "chromeos/constants/chromeos_features.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/base/metadata/metadata_impl_macros.h"
#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox.h" #include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/image_view.h" #include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/flex_layout_view.h" #include "ui/views/layout/flex_layout_view.h"
#include "ui/views/vector_icons.h"
namespace { namespace {
@@ -34,8 +29,6 @@ constexpr int kGlanceablesTaskViewHeight = 48;
constexpr int kGlanceablesTaskFooterHeight = 24; constexpr int kGlanceablesTaskFooterHeight = 24;
constexpr int kGlanceablesFooterMargin = 12; constexpr int kGlanceablesFooterMargin = 12;
constexpr int kGlanceableTaskVerticalMargin = 2; constexpr int kGlanceableTaskVerticalMargin = 2;
constexpr int kGlanceablesActionButtonLeftRightMargin = 4;
constexpr int kIconSize = 24;
} // namespace } // namespace
@@ -130,67 +123,9 @@ void TasksBubbleView::InitViews(ui::ListModel<GlanceablesTaskList>* task_list) {
&TasksBubbleView::SelectedTasksListChanged, base::Unretained(this))); &TasksBubbleView::SelectedTasksListChanged, base::Unretained(this)));
task_list_combo_box_view_->SetSelectedIndex(0); task_list_combo_box_view_->SetSelectedIndex(0);
tasks_footer_view_ = AddChildView(std::make_unique<views::FlexLayoutView>()); list_footer_view_ = AddChildView(
tasks_footer_view_->SetCrossAxisAlignment(views::LayoutAlignment::kCenter); std::make_unique<GlanceablesListFooterView>(base::BindRepeating(
tasks_footer_view_->SetMainAxisAlignment(views::LayoutAlignment::kStart); &TasksBubbleView::ActionButtonPressed, base::Unretained(this))));
tasks_footer_view_->SetOrientation(views::LayoutOrientation::kHorizontal);
tasks_footer_view_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kPreferred)
.WithOrder(1));
tasks_bubble_details_ =
tasks_footer_view_->AddChildView(std::make_unique<views::Label>());
tasks_bubble_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
// Views should not be individually selected for accessibility. Accessible
// name and behavior comes from the parent.
tasks_bubble_details_->GetViewAccessibility().OverrideIsIgnored(true);
tasks_bubble_details_->SetBackgroundColor(SK_ColorTRANSPARENT);
tasks_bubble_details_->SetAutoColorReadabilityEnabled(false);
if (chromeos::features::IsJellyEnabled()) {
tasks_bubble_details_->SetEnabledColorId(cros_tokens::kCrosSysSecondary);
} else {
tasks_bubble_details_->SetEnabledColorId(kColorAshTextColorSecondary);
}
// Create a transparent separator to push `action_button_` to the right-most
// corner of the tasks_header_view_.
separator_ =
tasks_footer_view_->AddChildView(std::make_unique<views::View>());
separator_->SetPreferredSize((gfx::Size(kRevampedTrayMenuWidth, 1)));
separator_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kPreferred)
.WithOrder(2));
action_button_ =
tasks_footer_view_->AddChildView(std::make_unique<views::LabelButton>(
base::BindRepeating(&TasksBubbleView::ActionButtonPressed,
base::Unretained(this)),
l10n_util::GetStringUTF16(
IDS_GLANCEABLES_TASKS_ACTION_BUTTON_LABEL)));
action_button_->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
action_button_->SetBorder(views::CreateEmptyBorder(
gfx::Insets::VH(0, kGlanceablesActionButtonLeftRightMargin)));
if (chromeos::features::IsJellyEnabled()) {
action_button_->SetTextColorId(views::Button::STATE_NORMAL,
cros_tokens::kCrosSysOnSurface);
action_button_->SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(vector_icons::kLaunchIcon,
cros_tokens::kCrosSysOnSurface,
kIconSize));
} else {
action_button_->SetTextColorId(views::Button::STATE_NORMAL,
kColorAshTextColorPrimary);
action_button_->SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(vector_icons::kLaunchIcon,
kColorAshTextColorPrimary, kIconSize));
}
ScheduleUpdateTasksList(); ScheduleUpdateTasksList();
} }
@@ -236,10 +171,7 @@ void TasksBubbleView::UpdateTasksList(const std::string& task_list_id,
++num_tasks_; ++num_tasks_;
} }
tasks_bubble_details_->SetText( list_footer_view_->UpdateItemsCount(num_tasks_shown_, num_tasks_);
l10n_util::GetStringFUTF16(IDS_GLANCEABLES_TASKS_DETAILS_FOOTER,
base::NumberToString16(num_tasks_shown_),
base::NumberToString16(num_tasks_)));
} }
BEGIN_METADATA(TasksBubbleView, views::View) BEGIN_METADATA(TasksBubbleView, views::View)

@@ -6,26 +6,21 @@
#define ASH_SYSTEM_UNIFIED_TASKS_BUBBLE_VIEW_H_ #define ASH_SYSTEM_UNIFIED_TASKS_BUBBLE_VIEW_H_
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "ash/glanceables/tasks/glanceables_task_view.h"
#include "ash/glanceables/tasks/glanceables_tasks_types.h" #include "ash/glanceables/tasks/glanceables_tasks_types.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/unified/glanceable_tray_child_bubble.h" #include "ash/system/unified/glanceable_tray_child_bubble.h"
#include "base/memory/raw_ptr.h" #include "base/memory/raw_ptr.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h" #include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/list_model.h" #include "ui/base/models/list_model.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/layout/flex_layout_view.h" #include "ui/views/layout/flex_layout_view.h"
#include "ui/views/metadata/view_factory.h"
namespace views { namespace views {
class Combobox; class Combobox;
class ImageView; class ImageView;
class Label;
class LabelButton;
} // namespace views } // namespace views
namespace ash { namespace ash {
class GlanceablesListFooterView;
class TasksComboboxModel; class TasksComboboxModel;
// 'TasksBubbleView' uses nested `FlexLayoutView`s to layout the tasks bubble. // 'TasksBubbleView' uses nested `FlexLayoutView`s to layout the tasks bubble.
@@ -61,10 +56,7 @@ class TasksComboboxModel;
// +----------------------------------------------------------------+ // +----------------------------------------------------------------+
// //
// +--------------------------------------------------------------+ // +--------------------------------------------------------------+
// |'tasks_footer_view_' | // |'list_footer_view_' |
// | +-----------------------+ +-------------+ +----------------+ |
// | |`tasks_bubble_details_`| |`separator_` | |`action_button_`| |
// | +-----------------------+ +-------------+ +----------------+ |
// +--------------------------------------------------------------+ // +--------------------------------------------------------------+
class ASH_EXPORT TasksBubbleView : public GlanceableTrayChildBubble { class ASH_EXPORT TasksBubbleView : public GlanceableTrayChildBubble {
@@ -95,7 +87,7 @@ class ASH_EXPORT TasksBubbleView : public GlanceableTrayChildBubble {
// Setup child views. // Setup child views.
void InitViews(ui::ListModel<GlanceablesTaskList>* task_list); void InitViews(ui::ListModel<GlanceablesTaskList>* task_list);
// Handles on-click behavior for `action_button_` // Handles on-click behavior for the "See all" button in `list_footer_view_`.
void ActionButtonPressed(); void ActionButtonPressed();
// Handles switching between tasks lists. // Handles switching between tasks lists.
@@ -115,12 +107,10 @@ class ASH_EXPORT TasksBubbleView : public GlanceableTrayChildBubble {
raw_ptr<views::ImageView, ExperimentalAsh> task_icon_view_ = nullptr; raw_ptr<views::ImageView, ExperimentalAsh> task_icon_view_ = nullptr;
raw_ptr<views::Combobox, ExperimentalAsh> task_list_combo_box_view_ = nullptr; raw_ptr<views::Combobox, ExperimentalAsh> task_list_combo_box_view_ = nullptr;
raw_ptr<views::FlexLayoutView, ExperimentalAsh> button_container_ = nullptr; raw_ptr<views::FlexLayoutView, ExperimentalAsh> button_container_ = nullptr;
raw_ptr<views::FlexLayoutView, ExperimentalAsh> tasks_footer_view_ = nullptr;
raw_ptr<views::Label, ExperimentalAsh> tasks_bubble_details_ = nullptr;
raw_ptr<views::View, ExperimentalAsh> separator_ = nullptr;
raw_ptr<views::LabelButton, ExperimentalAsh> action_button_ = nullptr;
raw_ptr<views::FlexLayoutView, ExperimentalAsh> task_items_container_view_ = raw_ptr<views::FlexLayoutView, ExperimentalAsh> task_items_container_view_ =
nullptr; nullptr;
raw_ptr<GlanceablesListFooterView, ExperimentalAsh> list_footer_view_ =
nullptr;
base::WeakPtrFactory<TasksBubbleView> weak_ptr_factory_{this}; base::WeakPtrFactory<TasksBubbleView> weak_ptr_factory_{this};
}; };