0

[Glanceables] Create label for classroom empty state

Create a label which shows when the classroom glanceable has no
assignment items to show. At the same time, also hide the footer when
when there are no items to show.

Bug: b/293309146
Change-Id: I3f8d102e3dd7992f81d95c42ed877c3b5d8f14ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4739774
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Commit-Queue: Matthew Mourgos <mmourgos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1179744}
This commit is contained in:
Matthew Mourgos
2023-08-04 18:59:39 +00:00
committed by Chromium LUCI CQ
parent d1f9ff15ca
commit 6e9eff6b93
9 changed files with 158 additions and 58 deletions

@ -6844,6 +6844,15 @@ New install
<message name="IDS_GLANCEABLES_ITEMS_TURNED_IN_AND_GRADED" desc="The teacher classroom glanceable shows upcoming/missed/completed assignments from Google Classroom. For each assignment item, this text displays the number of submissions turned in along side the total number of sumbissions. Then this text also displays the number of submissions currently graded.">
<ph name="NUM_TURNED_IN">$1<ex>8</ex></ph>/<ph name="TOTAL_NUM_OF_SUBMISSIONS">$2<ex>22</ex></ph> turned in • <ph name="NUM_GRADED">$3<ex>5</ex></ph> graded
</message>
<message name="IDS_GLANCEABLES_CLASSROOM_STUDENT_EMPTY_ITEM_DUE_LIST" desc="The student classroom glanceable shows this label when there are no assignments to show for the 'Due Soon' or 'No Due Date' list.">
Nothing on this to-do list.
</message>
<message name="IDS_GLANCEABLES_CLASSROOM_STUDENT_EMPTY_ITEM_MISSING_LIST" desc="The student classroom glanceable shows this label when there are no assignments to show for the 'Missing' list.">
Looks like nothing is missing. Nice work!
</message>
<message name="IDS_GLANCEABLES_CLASSROOM_STUDENT_EMPTY_ITEM_DONE_LIST" desc="The student classroom glanceable shows this label when there are no assignments to show for the 'Done' list.">
No assignments completed yet.
</message>
<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_ITEMS">$1<ex>5</ex></ph> out of <ph name="NUM_TOTAL_ITEMS">$2<ex>10</ex></ph>
</message>

@ -0,0 +1 @@
5bbf185abb2f18928b0e8cb6a079f38b5e4684e2

@ -0,0 +1 @@
a1b71d6f43fdb7edf0bae866f3f1ab640273c4f8

@ -0,0 +1 @@
9994950416c97b0db56f4ab0203a95dd147e4f3c

@ -21,6 +21,7 @@ enum class GlanceablesViewId {
// `ClassroomBubbleBaseView`.
kClassroomBubbleComboBox,
kClassroomBubbleListContainer,
kClassroomBubbleEmptyListLabel,
kClassroomBubbleListFooter,
// `GlanceablesClassroomItemView`.

@ -15,6 +15,7 @@
#include "ash/glanceables/glanceables_v2_controller.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/style/typography.h"
#include "base/functional/bind.h"
#include "base/types/cxx23_to_underlying.h"
#include "ui/base/metadata/metadata_impl_macros.h"
@ -27,6 +28,7 @@
#include "ui/views/background.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/view_class_properties.h"
@ -93,6 +95,19 @@ ClassroomBubbleBaseView::ClassroomBubbleBaseView(
views::BoxLayout::Orientation::kVertical));
layout->set_between_child_spacing(2);
const auto* const typography_provider = TypographyProvider::Get();
empty_list_label_ = AddChildView(
views::Builder<views::Label>()
.SetProperty(views::kMarginsKey, gfx::Insets::TLBR(24, 0, 32, 0))
.SetEnabledColorId(cros_tokens::kCrosSysOnSurface)
.SetFontList(typography_provider->ResolveTypographyToken(
TypographyToken::kCrosButton2))
.SetLineHeight(typography_provider->ResolveLineHeight(
TypographyToken::kCrosButton2))
.SetID(base::to_underlying(
GlanceablesViewId::kClassroomBubbleEmptyListLabel))
.Build());
list_footer_view_ = AddChildView(
std::make_unique<GlanceablesListFooterView>(base::BindRepeating(
&ClassroomBubbleBaseView::OnSeeAllPressed, base::Unretained(this))));
@ -133,6 +148,11 @@ void ClassroomBubbleBaseView::OnGetAssignments(
}
list_footer_view_->UpdateItemsCount(list_container_view_->children().size(),
assignments.size());
const bool is_list_empty = list_container_view_->children().size() == 0;
empty_list_label_->SetVisible(is_list_empty);
list_footer_view_->SetVisible(!is_list_empty);
if (list_container_view_->children().size() != old_item_count) {
PreferredSizeChanged();
}

@ -13,6 +13,7 @@ class GURL;
namespace views {
class Combobox;
class Label;
}
namespace ui {
@ -59,6 +60,7 @@ class ASH_EXPORT ClassroomBubbleBaseView : public GlanceableTrayChildBubble {
raw_ptr<GlanceablesListFooterView, ExperimentalAsh> list_footer_view_ =
nullptr;
raw_ptr<GlanceablesProgressBarView, ExperimentalAsh> progress_bar_ = nullptr;
raw_ptr<views::Label, ExperimentalAsh> empty_list_label_ = nullptr;
base::WeakPtrFactory<ClassroomBubbleBaseView> weak_ptr_factory_{this};
};

@ -13,12 +13,15 @@
#include "ash/glanceables/common/glanceables_progress_bar_view.h"
#include "ash/glanceables/glanceables_v2_controller.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/tray/detailed_view_delegate.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/strings/string_piece_forward.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "url/gurl.h"
namespace ash {
@ -135,14 +138,22 @@ void ClassroomBubbleStudentView::SelectedAssignmentListChanged() {
weak_ptr_factory_.GetWeakPtr());
switch (kStudentAssignmentsListTypeOrdered[selected_index]) {
case StudentAssignmentsListType::kAssigned:
empty_list_label_->SetText(l10n_util::GetStringUTF16(
IDS_GLANCEABLES_CLASSROOM_STUDENT_EMPTY_ITEM_DUE_LIST));
return client->GetStudentAssignmentsWithApproachingDueDate(
std::move(callback));
case StudentAssignmentsListType::kNoDueDate:
empty_list_label_->SetText(l10n_util::GetStringUTF16(
IDS_GLANCEABLES_CLASSROOM_STUDENT_EMPTY_ITEM_DUE_LIST));
return client->GetStudentAssignmentsWithoutDueDate(std::move(callback));
case StudentAssignmentsListType::kMissing:
empty_list_label_->SetText(l10n_util::GetStringUTF16(
IDS_GLANCEABLES_CLASSROOM_STUDENT_EMPTY_ITEM_MISSING_LIST));
return client->GetStudentAssignmentsWithMissedDueDate(
std::move(callback));
case StudentAssignmentsListType::kDone:
empty_list_label_->SetText(l10n_util::GetStringUTF16(
IDS_GLANCEABLES_CLASSROOM_STUDENT_EMPTY_ITEM_DONE_LIST));
return client->GetCompletedStudentAssignments(std::move(callback));
}
}

@ -89,6 +89,19 @@ class TestClient : public GlanceablesClassroomClient {
MOCK_METHOD(void, OnGlanceablesBubbleClosed, (), (override));
};
std::vector<std::unique_ptr<GlanceablesClassroomAssignment>> CreateAssignments(
int count) {
std::vector<std::unique_ptr<GlanceablesClassroomAssignment>> assignments;
for (int i = 0; i < count; ++i) {
assignments.push_back(std::make_unique<GlanceablesClassroomAssignment>(
"Course title", base::StringPrintf("Course work title %d", i + 1),
GURL(base::StringPrintf("https://classroom.google.com/test-link-%d",
i + 1)),
absl::nullopt, base::Time(), absl::nullopt));
}
return assignments;
}
} // namespace
class ClassroomBubbleViewTest : public AshTestBase {
@ -116,11 +129,22 @@ class ClassroomBubbleViewTest : public AshTestBase {
base::to_underlying(GlanceablesViewId::kClassroomBubbleListContainer)));
}
const views::View* GetEmptyListLabel() const {
return views::AsViewClass<views::View>(
view_->GetViewByID(base::to_underlying(
GlanceablesViewId::kClassroomBubbleEmptyListLabel)));
}
const views::Label* GetListFooterItemsCountLabel() const {
return views::AsViewClass<views::Label>(view_->GetViewByID(
base::to_underlying(GlanceablesViewId::kListFooterItemsCountLabel)));
}
const views::View* GetListFooter() const {
return views::AsViewClass<views::View>(view_->GetViewByID(
base::to_underlying(GlanceablesViewId::kClassroomBubbleListFooter)));
}
views::LabelButton* GetListFooterSeeAllButton() const {
return views::AsViewClass<views::LabelButton>(view_->GetViewByID(
base::to_underlying(GlanceablesViewId::kListFooterSeeAllButton)));
@ -202,36 +226,39 @@ TEST_F(ClassroomBubbleStudentViewTest,
CallsClassroomClientAfterChangingActiveList) {
ASSERT_TRUE(GetComboBoxView());
ASSERT_TRUE(GetListFooterSeeAllButton());
EXPECT_CALL(
classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/a/not-turned-in/all")));
LeftClickOn(GetListFooterSeeAllButton());
EXPECT_FALSE(GetListFooter()->GetVisible());
EXPECT_CALL(classroom_client_, GetStudentAssignmentsWithoutDueDate(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
std::move(cb).Run(/*success=*/true, CreateAssignments(3));
});
GetComboBoxView()->MenuSelectionAt(1);
// Trigger layout after receiving new items.
widget_->LayoutRootViewIfNecessary();
EXPECT_CALL(
classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/a/not-turned-in/all")));
EXPECT_TRUE(GetListFooter()->GetVisible());
LeftClickOn(GetListFooterSeeAllButton());
EXPECT_CALL(classroom_client_, GetStudentAssignmentsWithMissedDueDate(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
std::move(cb).Run(/*success=*/true, CreateAssignments(3));
});
GetComboBoxView()->MenuSelectionAt(2);
// Trigger layout after receiving new items.
widget_->LayoutRootViewIfNecessary();
EXPECT_CALL(classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/a/missing/all")));
LeftClickOn(GetListFooterSeeAllButton());
EXPECT_CALL(classroom_client_, GetCompletedStudentAssignments(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
std::move(cb).Run(/*success=*/true, CreateAssignments(3));
});
GetComboBoxView()->MenuSelectionAt(3);
// Trigger layout after receiving new items.
widget_->LayoutRootViewIfNecessary();
EXPECT_CALL(
classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/a/turned-in/all")));
@ -243,16 +270,13 @@ TEST_F(ClassroomBubbleTeacherViewTest,
ASSERT_TRUE(GetComboBoxView());
ASSERT_TRUE(GetListFooterSeeAllButton());
EXPECT_CALL(
classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/ta/not-reviewed/all")));
LeftClickOn(GetListFooterSeeAllButton());
EXPECT_CALL(classroom_client_, GetTeacherAssignmentsRecentlyDue(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
std::move(cb).Run(/*success=*/true, CreateAssignments(3));
});
GetComboBoxView()->MenuSelectionAt(1);
// Trigger layout after receiving new items.
widget_->LayoutRootViewIfNecessary();
EXPECT_CALL(
classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/ta/not-reviewed/all")));
@ -260,9 +284,11 @@ TEST_F(ClassroomBubbleTeacherViewTest,
EXPECT_CALL(classroom_client_, GetTeacherAssignmentsWithoutDueDate(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
std::move(cb).Run(/*success=*/true, CreateAssignments(3));
});
GetComboBoxView()->MenuSelectionAt(2);
// Trigger layout after receiving new items.
widget_->LayoutRootViewIfNecessary();
EXPECT_CALL(
classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/ta/not-reviewed/all")));
@ -270,9 +296,11 @@ TEST_F(ClassroomBubbleTeacherViewTest,
EXPECT_CALL(classroom_client_, GetGradedTeacherAssignments(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
std::move(cb).Run(/*success=*/true, CreateAssignments(3));
});
GetComboBoxView()->MenuSelectionAt(3);
// Trigger layout after receiving new items.
widget_->LayoutRootViewIfNecessary();
EXPECT_CALL(
classroom_client_,
OpenUrl(GURL("https://classroom.google.com/u/0/ta/reviewed/all")));
@ -282,25 +310,16 @@ TEST_F(ClassroomBubbleTeacherViewTest,
TEST_F(ClassroomBubbleStudentViewTest, RendersListItems) {
EXPECT_CALL(classroom_client_, GetCompletedStudentAssignments(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::vector<std::unique_ptr<GlanceablesClassroomAssignment>>
assignments;
for (size_t i = 0; i < 5; ++i) {
assignments.push_back(
std::make_unique<GlanceablesClassroomAssignment>(
"Course title",
base::StringPrintf("Course work title %zu", i + 1),
GURL(base::StringPrintf(
"https://classroom.google.com/test-link-%zu", i + 1)),
absl::nullopt, base::Time(), absl::nullopt));
}
std::move(cb).Run(/*success=*/true, std::move(assignments));
std::move(cb).Run(/*success=*/true, CreateAssignments(5));
});
ASSERT_TRUE(GetComboBoxView());
ASSERT_TRUE(GetListContainerView());
EXPECT_FALSE(GetListFooter()->GetVisible());
GetComboBoxView()->MenuSelectionAt(3);
EXPECT_EQ(GetListContainerView()->children().size(), 3u); // No more than 3.
EXPECT_TRUE(GetListFooter()->GetVisible());
ASSERT_TRUE(GetListFooterItemsCountLabel());
EXPECT_EQ(GetListFooterItemsCountLabel()->GetText(), u"Showing 3 out of 5");
}
@ -308,19 +327,7 @@ TEST_F(ClassroomBubbleStudentViewTest, RendersListItems) {
TEST_F(ClassroomBubbleTeacherViewTest, RendersListItems) {
EXPECT_CALL(classroom_client_, GetGradedTeacherAssignments(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::vector<std::unique_ptr<GlanceablesClassroomAssignment>>
assignments;
for (size_t i = 0; i < 5; ++i) {
assignments.push_back(
std::make_unique<GlanceablesClassroomAssignment>(
"Course title",
base::StringPrintf("Course work title %zu", i + 1),
GURL(base::StringPrintf(
"https://classroom.google.com/test-link-%zu", i + 1)),
absl::nullopt, base::Time(),
GlanceablesClassroomAggregatedSubmissionsState(0, 0, 0)));
}
std::move(cb).Run(/*success=*/true, std::move(assignments));
std::move(cb).Run(/*success=*/true, CreateAssignments(5));
});
ASSERT_TRUE(GetComboBoxView());
ASSERT_TRUE(GetListContainerView());
@ -332,16 +339,70 @@ TEST_F(ClassroomBubbleTeacherViewTest, RendersListItems) {
EXPECT_EQ(GetListFooterItemsCountLabel()->GetText(), u"Showing 3 out of 5");
}
TEST_F(ClassroomBubbleStudentViewTest, RendersEmptyListLabel) {
ASSERT_TRUE(GetComboBoxView());
ASSERT_TRUE(GetListContainerView());
EXPECT_FALSE(GetListFooter()->GetVisible());
EXPECT_TRUE(GetEmptyListLabel()->GetVisible());
EXPECT_CALL(classroom_client_, GetStudentAssignmentsWithoutDueDate(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, CreateAssignments(5));
});
GetComboBoxView()->MenuSelectionAt(1);
EXPECT_EQ(GetListContainerView()->children().size(), 3u); // No more than 3.
// The empty list label should be hidden, and the footer shown.
EXPECT_TRUE(GetListFooter()->GetVisible());
EXPECT_FALSE(GetEmptyListLabel()->GetVisible());
EXPECT_EQ(GetListFooterItemsCountLabel()->GetText(), u"Showing 3 out of 5");
EXPECT_CALL(classroom_client_, GetStudentAssignmentsWithMissedDueDate(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
});
GetComboBoxView()->MenuSelectionAt(2);
EXPECT_EQ(GetListContainerView()->children().size(), 0u);
// The empty list label should be shown, and the footer hidden.
EXPECT_FALSE(GetListFooter()->GetVisible());
EXPECT_TRUE(GetEmptyListLabel()->GetVisible());
}
TEST_F(ClassroomBubbleTeacherViewTest, RendersEmptyListLabel) {
ASSERT_TRUE(GetComboBoxView());
ASSERT_TRUE(GetListContainerView());
EXPECT_FALSE(GetListFooter()->GetVisible());
EXPECT_TRUE(GetEmptyListLabel()->GetVisible());
EXPECT_CALL(classroom_client_, GetTeacherAssignmentsRecentlyDue(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, CreateAssignments(5));
});
GetComboBoxView()->MenuSelectionAt(1);
EXPECT_EQ(GetListContainerView()->children().size(), 3u); // No more than 3.
// The empty list label should be hidden, and the footer shown.
EXPECT_TRUE(GetListFooter()->GetVisible());
EXPECT_FALSE(GetEmptyListLabel()->GetVisible());
EXPECT_EQ(GetListFooterItemsCountLabel()->GetText(), u"Showing 3 out of 5");
EXPECT_CALL(classroom_client_, GetTeacherAssignmentsWithoutDueDate(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::move(cb).Run(/*success=*/true, {});
});
GetComboBoxView()->MenuSelectionAt(2);
EXPECT_EQ(GetListContainerView()->children().size(), 0u);
// The empty list label should be shown, and the footer hidden.
EXPECT_FALSE(GetListFooter()->GetVisible());
EXPECT_TRUE(GetEmptyListLabel()->GetVisible());
}
TEST_F(ClassroomBubbleStudentViewTest, OpensClassroomUrlForListItem) {
EXPECT_CALL(classroom_client_, GetCompletedStudentAssignments(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::vector<std::unique_ptr<GlanceablesClassroomAssignment>>
assignments;
assignments.push_back(std::make_unique<GlanceablesClassroomAssignment>(
"Course title", "Course work title",
GURL("https://classroom.google.com/test-link"), absl::nullopt,
base::Time(), absl::nullopt));
std::move(cb).Run(/*success=*/true, std::move(assignments));
std::move(cb).Run(/*success=*/true, CreateAssignments(1));
});
ASSERT_TRUE(GetComboBoxView());
GetComboBoxView()->MenuSelectionAt(3);
@ -353,21 +414,14 @@ TEST_F(ClassroomBubbleStudentViewTest, OpensClassroomUrlForListItem) {
ASSERT_EQ(GetListContainerView()->children().size(), 1u);
EXPECT_CALL(classroom_client_,
OpenUrl(GURL("https://classroom.google.com/test-link")));
OpenUrl(GURL("https://classroom.google.com/test-link-1")));
LeftClickOn(GetListContainerView()->children().at(0));
}
TEST_F(ClassroomBubbleTeacherViewTest, OpensClassroomUrlForListItem) {
EXPECT_CALL(classroom_client_, GetGradedTeacherAssignments(_))
.WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
std::vector<std::unique_ptr<GlanceablesClassroomAssignment>>
assignments;
assignments.push_back(std::make_unique<GlanceablesClassroomAssignment>(
"Course title", "Course work title",
GURL("https://classroom.google.com/test-link"), absl::nullopt,
base::Time(),
GlanceablesClassroomAggregatedSubmissionsState(0, 0, 0)));
std::move(cb).Run(/*success=*/true, std::move(assignments));
std::move(cb).Run(/*success=*/true, CreateAssignments(1));
});
ASSERT_TRUE(GetComboBoxView());
GetComboBoxView()->MenuSelectionAt(3);
@ -379,7 +433,7 @@ TEST_F(ClassroomBubbleTeacherViewTest, OpensClassroomUrlForListItem) {
ASSERT_EQ(GetListContainerView()->children().size(), 1u);
EXPECT_CALL(classroom_client_,
OpenUrl(GURL("https://classroom.google.com/test-link")));
OpenUrl(GURL("https://classroom.google.com/test-link-1")));
LeftClickOn(GetListContainerView()->children().at(0));
}