Add up next view
This CL introduces an "Up next" view in the CalendarView where users can see their upcoming events. Screenshot: http://shortn/_qA0fSSZL56 Smaller refactors include: - Reduce CalendarEventListItemViewJelly controller dependency so it can be re-used in the UpNext view. - LayoutManager resizes CalendarView scroll view to dynamically make space for the CalendarUpNextView. Bug: b:258165425 Change-Id: I478bd4a5f33c222eaec0a053ac4e949f4bd9e18f Low-Coverage-Reason: Low in existing class, just made a small refactor wrapping existing params in a struct, no new functionality added Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4013467 Commit-Queue: Sam Cackett <samcackett@google.com> Reviewed-by: Jiaming Cheng <jiamingc@chromium.org> Auto-Submit: Sam Cackett <samcackett@google.com> Cr-Commit-Position: refs/heads/main@{#1072224}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
c8131acb55
commit
f4c17f031b
ash
BUILD.gnash_strings.grd
ash_strings_grd
system
time
calendar_event_list_item_view_jelly.cccalendar_event_list_item_view_jelly.hcalendar_event_list_item_view_jelly_unittest.cccalendar_event_list_view.cccalendar_model.cccalendar_model.hcalendar_model_unittest.cccalendar_up_next_view.cccalendar_up_next_view.hcalendar_up_next_view_unittest.cccalendar_view.cccalendar_view.hcalendar_view_controller.cccalendar_view_controller.hcalendar_view_unittest.cc
@@ -1726,6 +1726,8 @@ component("ash") {
|
|||||||
"system/time/calendar_model.h",
|
"system/time/calendar_model.h",
|
||||||
"system/time/calendar_month_view.cc",
|
"system/time/calendar_month_view.cc",
|
||||||
"system/time/calendar_month_view.h",
|
"system/time/calendar_month_view.h",
|
||||||
|
"system/time/calendar_up_next_view.cc",
|
||||||
|
"system/time/calendar_up_next_view.h",
|
||||||
"system/time/calendar_utils.cc",
|
"system/time/calendar_utils.cc",
|
||||||
"system/time/calendar_utils.h",
|
"system/time/calendar_utils.h",
|
||||||
"system/time/calendar_view.cc",
|
"system/time/calendar_view.cc",
|
||||||
@@ -3058,6 +3060,7 @@ test("ash_unittests") {
|
|||||||
"system/time/calendar_month_view_unittest.cc",
|
"system/time/calendar_month_view_unittest.cc",
|
||||||
"system/time/calendar_unittest_utils.cc",
|
"system/time/calendar_unittest_utils.cc",
|
||||||
"system/time/calendar_unittest_utils.h",
|
"system/time/calendar_unittest_utils.h",
|
||||||
|
"system/time/calendar_up_next_view_unittest.cc",
|
||||||
"system/time/calendar_utils_unittest.cc",
|
"system/time/calendar_utils_unittest.cc",
|
||||||
"system/time/calendar_view_controller_unittest.cc",
|
"system/time/calendar_view_controller_unittest.cc",
|
||||||
"system/time/calendar_view_unittest.cc",
|
"system/time/calendar_view_unittest.cc",
|
||||||
|
@@ -4199,6 +4199,10 @@ Connect your device to power.
|
|||||||
Open in Google Calendar
|
Open in Google Calendar
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
<message name="IDS_ASH_CALENDAR_UP_NEXT" desc="Header label for the Calendar Up next view.">
|
||||||
|
Up next
|
||||||
|
</message>
|
||||||
|
|
||||||
<message name="IDS_ASH_STATUS_TRAY_PROGRESS_BAR_ACCESSIBLE_NAME" desc="The accessible name for the progress bar shown in the status tray.">
|
<message name="IDS_ASH_STATUS_TRAY_PROGRESS_BAR_ACCESSIBLE_NAME" desc="The accessible name for the progress bar shown in the status tray.">
|
||||||
Loading
|
Loading
|
||||||
</message>
|
</message>
|
||||||
|
1
ash/ash_strings_grd/IDS_ASH_CALENDAR_UP_NEXT.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_ASH_CALENDAR_UP_NEXT.png.sha1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
05da4a226414b5fea76a977dcf45df6a3272546e
|
@@ -90,7 +90,8 @@ class CalendarEventListItemDot : public views::View {
|
|||||||
// Creates and returns a label containing the event summary.
|
// Creates and returns a label containing the event summary.
|
||||||
views::Builder<views::Label> CreateSummaryLabel(
|
views::Builder<views::Label> CreateSummaryLabel(
|
||||||
const std::string& event_summary,
|
const std::string& event_summary,
|
||||||
const std::u16string& tooltip_text) {
|
const std::u16string& tooltip_text,
|
||||||
|
const int& max_width) {
|
||||||
return views::Builder<views::Label>(
|
return views::Builder<views::Label>(
|
||||||
bubble_utils::CreateLabel(
|
bubble_utils::CreateLabel(
|
||||||
bubble_utils::TypographyStyle::kButton1,
|
bubble_utils::TypographyStyle::kButton1,
|
||||||
@@ -100,6 +101,9 @@ views::Builder<views::Label> CreateSummaryLabel(
|
|||||||
.SetID(kSummaryLabelID)
|
.SetID(kSummaryLabelID)
|
||||||
.SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
|
.SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
|
||||||
.SetAutoColorReadabilityEnabled(false)
|
.SetAutoColorReadabilityEnabled(false)
|
||||||
|
.SetMultiLine(true)
|
||||||
|
.SetMaxLines(1)
|
||||||
|
.SetMaximumWidth(max_width)
|
||||||
.SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL)
|
.SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL)
|
||||||
.SetSubpixelRenderingEnabled(false)
|
.SetSubpixelRenderingEnabled(false)
|
||||||
.SetTextContext(CONTEXT_CALENDAR_DATE)
|
.SetTextContext(CONTEXT_CALENDAR_DATE)
|
||||||
@@ -126,20 +130,21 @@ views::Builder<views::Label> CreateTimeLabel(
|
|||||||
|
|
||||||
CalendarEventListItemViewJelly::CalendarEventListItemViewJelly(
|
CalendarEventListItemViewJelly::CalendarEventListItemViewJelly(
|
||||||
CalendarViewController* calendar_view_controller,
|
CalendarViewController* calendar_view_controller,
|
||||||
|
SelectedDateParams selected_date_params,
|
||||||
google_apis::calendar::CalendarEvent event,
|
google_apis::calendar::CalendarEvent event,
|
||||||
const bool round_top_corners,
|
const bool round_top_corners,
|
||||||
const bool round_bottom_corners)
|
const bool round_bottom_corners,
|
||||||
|
const int max_width)
|
||||||
: ActionableView(TrayPopupInkDropStyle::FILL_BOUNDS),
|
: ActionableView(TrayPopupInkDropStyle::FILL_BOUNDS),
|
||||||
calendar_view_controller_(calendar_view_controller),
|
calendar_view_controller_(calendar_view_controller),
|
||||||
|
selected_date_params_(selected_date_params),
|
||||||
event_url_(event.html_link()) {
|
event_url_(event.html_link()) {
|
||||||
SetLayoutManager(std::make_unique<views::FillLayout>());
|
SetLayoutManager(std::make_unique<views::FillLayout>());
|
||||||
|
|
||||||
DCHECK(calendar_view_controller_->selected_date().has_value());
|
|
||||||
|
|
||||||
const auto [start_time, end_time] = calendar_utils::GetStartAndEndTime(
|
const auto [start_time, end_time] = calendar_utils::GetStartAndEndTime(
|
||||||
&event, calendar_view_controller->selected_date().value(),
|
&event, selected_date_params_.selected_date,
|
||||||
calendar_view_controller->selected_date_midnight(),
|
selected_date_params_.selected_date_midnight,
|
||||||
calendar_view_controller->selected_date_midnight_utc());
|
selected_date_params_.selected_date_midnight_utc);
|
||||||
const auto [start_time_accessible_name, end_time_accessible_name] =
|
const auto [start_time_accessible_name, end_time_accessible_name] =
|
||||||
event_date_formatter_util::GetStartAndEndTimeAccessibleNames(start_time,
|
event_date_formatter_util::GetStartAndEndTimeAccessibleNames(start_time,
|
||||||
end_time);
|
end_time);
|
||||||
@@ -169,8 +174,8 @@ CalendarEventListItemViewJelly::CalendarEventListItemViewJelly(
|
|||||||
std::u16string formatted_time_text;
|
std::u16string formatted_time_text;
|
||||||
if (calendar_utils::IsMultiDayEvent(&event) || event.all_day_event()) {
|
if (calendar_utils::IsMultiDayEvent(&event) || event.all_day_event()) {
|
||||||
formatted_time_text = event_date_formatter_util::GetMultiDayText(
|
formatted_time_text = event_date_formatter_util::GetMultiDayText(
|
||||||
&event, calendar_view_controller->selected_date_midnight(),
|
&event, selected_date_params_.selected_date_midnight,
|
||||||
calendar_view_controller->selected_date_midnight_utc());
|
selected_date_params_.selected_date_midnight_utc);
|
||||||
} else {
|
} else {
|
||||||
formatted_time_text =
|
formatted_time_text =
|
||||||
event_date_formatter_util::GetFormattedInterval(start_time, end_time);
|
event_date_formatter_util::GetFormattedInterval(start_time, end_time);
|
||||||
@@ -188,7 +193,8 @@ CalendarEventListItemViewJelly::CalendarEventListItemViewJelly(
|
|||||||
views::Builder<views::View>()
|
views::Builder<views::View>()
|
||||||
.SetLayoutManager(std::make_unique<views::BoxLayout>(
|
.SetLayoutManager(std::make_unique<views::BoxLayout>(
|
||||||
views::BoxLayout::Orientation::kVertical))
|
views::BoxLayout::Orientation::kVertical))
|
||||||
.AddChild(CreateSummaryLabel(event.summary(), tooltip_text))
|
.AddChild(CreateSummaryLabel(event.summary(), tooltip_text,
|
||||||
|
max_width))
|
||||||
.AddChild(CreateTimeLabel(formatted_time_text, tooltip_text)))
|
.AddChild(CreateTimeLabel(formatted_time_text, tooltip_text)))
|
||||||
.Build());
|
.Build());
|
||||||
}
|
}
|
||||||
@@ -209,10 +215,9 @@ bool CalendarEventListItemViewJelly::PerformAction(const ui::Event& event) {
|
|||||||
|
|
||||||
GURL finalized_url;
|
GURL finalized_url;
|
||||||
bool opened_pwa = false;
|
bool opened_pwa = false;
|
||||||
DCHECK(calendar_view_controller_->selected_date().has_value());
|
|
||||||
Shell::Get()->system_tray_model()->client()->ShowCalendarEvent(
|
Shell::Get()->system_tray_model()->client()->ShowCalendarEvent(
|
||||||
event_url_, calendar_view_controller_->selected_date_midnight(),
|
event_url_, selected_date_params_.selected_date_midnight, opened_pwa,
|
||||||
opened_pwa, finalized_url);
|
finalized_url);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,12 @@ constexpr int kTimeLabelID = 101;
|
|||||||
|
|
||||||
class CalendarViewController;
|
class CalendarViewController;
|
||||||
|
|
||||||
|
struct SelectedDateParams {
|
||||||
|
base::Time selected_date;
|
||||||
|
base::Time selected_date_midnight;
|
||||||
|
base::Time selected_date_midnight_utc;
|
||||||
|
};
|
||||||
|
|
||||||
// This view displays a jelly version of a calendar event entry.
|
// This view displays a jelly version of a calendar event entry.
|
||||||
class ASH_EXPORT CalendarEventListItemViewJelly : public ActionableView {
|
class ASH_EXPORT CalendarEventListItemViewJelly : public ActionableView {
|
||||||
public:
|
public:
|
||||||
@@ -30,9 +36,11 @@ class ASH_EXPORT CalendarEventListItemViewJelly : public ActionableView {
|
|||||||
|
|
||||||
CalendarEventListItemViewJelly(
|
CalendarEventListItemViewJelly(
|
||||||
CalendarViewController* calendar_view_controller,
|
CalendarViewController* calendar_view_controller,
|
||||||
|
SelectedDateParams selected_date_params,
|
||||||
google_apis::calendar::CalendarEvent event,
|
google_apis::calendar::CalendarEvent event,
|
||||||
const bool round_top_corners,
|
const bool round_top_corners,
|
||||||
const bool round_bottom_corners);
|
const bool round_bottom_corners,
|
||||||
|
const int max_width = 0);
|
||||||
CalendarEventListItemViewJelly(const CalendarEventListItemViewJelly& other) =
|
CalendarEventListItemViewJelly(const CalendarEventListItemViewJelly& other) =
|
||||||
delete;
|
delete;
|
||||||
CalendarEventListItemViewJelly& operator=(
|
CalendarEventListItemViewJelly& operator=(
|
||||||
@@ -51,6 +59,8 @@ class ASH_EXPORT CalendarEventListItemViewJelly : public ActionableView {
|
|||||||
// Unowned.
|
// Unowned.
|
||||||
CalendarViewController* const calendar_view_controller_;
|
CalendarViewController* const calendar_view_controller_;
|
||||||
|
|
||||||
|
const SelectedDateParams selected_date_params_;
|
||||||
|
|
||||||
// The URL for the meeting event.
|
// The URL for the meeting event.
|
||||||
const GURL event_url_;
|
const GURL event_url_;
|
||||||
};
|
};
|
||||||
|
@@ -53,13 +53,18 @@ class CalendarViewEventListItemViewJellyTest : public AshTestBase {
|
|||||||
void CreateEventListItemView(base::Time date,
|
void CreateEventListItemView(base::Time date,
|
||||||
google_apis::calendar::CalendarEvent* event,
|
google_apis::calendar::CalendarEvent* event,
|
||||||
bool round_top_corners = false,
|
bool round_top_corners = false,
|
||||||
bool round_bottom_corners = false) {
|
bool round_bottom_corners = false,
|
||||||
|
int max_width = 0) {
|
||||||
event_list_item_view_jelly_.reset();
|
event_list_item_view_jelly_.reset();
|
||||||
controller_->UpdateMonth(date);
|
controller_->UpdateMonth(date);
|
||||||
controller_->selected_date_ = date;
|
controller_->selected_date_ = date;
|
||||||
event_list_item_view_jelly_ =
|
event_list_item_view_jelly_ =
|
||||||
std::make_unique<CalendarEventListItemViewJelly>(
|
std::make_unique<CalendarEventListItemViewJelly>(
|
||||||
controller_.get(), *event, round_top_corners, round_bottom_corners);
|
controller_.get(),
|
||||||
|
SelectedDateParams{controller_->selected_date().value(),
|
||||||
|
controller_->selected_date_midnight(),
|
||||||
|
controller_->selected_date_midnight_utc()},
|
||||||
|
*event, round_top_corners, round_bottom_corners, max_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetSelectedDateInController(base::Time date) {
|
void SetSelectedDateInController(base::Time date) {
|
||||||
@@ -167,4 +172,28 @@ TEST_F(CalendarViewEventListItemViewJellyTest, AllRoundedCorners) {
|
|||||||
background_layer->rounded_corner_radii());
|
background_layer->rounded_corner_radii());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CalendarViewEventListItemViewJellyTest, MaxLabelWidth) {
|
||||||
|
base::Time date;
|
||||||
|
ASSERT_TRUE(base::Time::FromString("22 Nov 2021 00:00 UTC", &date));
|
||||||
|
SetSelectedDateInController(date);
|
||||||
|
const char* start_time_string = "22 Nov 2021 09:00 GMT";
|
||||||
|
const char* end_time_string = "22 Nov 2021 10:00 GMT";
|
||||||
|
const auto event = CreateEvent(start_time_string, end_time_string);
|
||||||
|
|
||||||
|
// If we don't set `max_width`, it should default to 0 (which the
|
||||||
|
// `views::Label`) will ignore).
|
||||||
|
CreateEventListItemView(date, event.get(), /*round_top_corners*/ true,
|
||||||
|
/*round_bottom_corners*/ true);
|
||||||
|
|
||||||
|
EXPECT_EQ(GetSummaryLabel()->GetMaximumWidth(), 0);
|
||||||
|
|
||||||
|
// If we set a `max_width`, it should exist on the Summary Label.
|
||||||
|
const auto max_width = 200;
|
||||||
|
CreateEventListItemView(date, event.get(), /*round_top_corners*/ true,
|
||||||
|
/*round_bottom_corners*/ true,
|
||||||
|
/*max_width=*/max_width);
|
||||||
|
|
||||||
|
EXPECT_EQ(GetSummaryLabel()->GetMaximumWidth(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ash
|
} // namespace ash
|
||||||
|
@@ -232,7 +232,12 @@ std::unique_ptr<views::View> CalendarEventListView::CreateChildEventListView(
|
|||||||
++it) {
|
++it) {
|
||||||
container->AddChildView(std::make_unique<CalendarEventListItemViewJelly>(
|
container->AddChildView(std::make_unique<CalendarEventListItemViewJelly>(
|
||||||
/*calendar_view_controller=*/calendar_view_controller_,
|
/*calendar_view_controller=*/calendar_view_controller_,
|
||||||
/*event=*/*it,
|
/*selected_date_params=*/
|
||||||
|
SelectedDateParams{
|
||||||
|
calendar_view_controller_->selected_date().value(),
|
||||||
|
calendar_view_controller_->selected_date_midnight(),
|
||||||
|
calendar_view_controller_->selected_date_midnight_utc()}, /*event=*/
|
||||||
|
*it,
|
||||||
/*round_top_corners=*/it == events.begin(),
|
/*round_top_corners=*/it == events.begin(),
|
||||||
/*round_bottom_corners=*/it->id() == events.rbegin()->id()));
|
/*round_bottom_corners=*/it->id() == events.rbegin()->id()));
|
||||||
}
|
}
|
||||||
|
@@ -89,6 +89,45 @@ void SortByDateAscending(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EventStartsInTenMins(const CalendarEvent& event,
|
||||||
|
const base::Time& now_local) {
|
||||||
|
const int start_time_difference_in_mins =
|
||||||
|
(ash::calendar_utils::GetStartTimeAdjusted(&event) - now_local)
|
||||||
|
.InMinutes();
|
||||||
|
|
||||||
|
return (0 <= start_time_difference_in_mins &&
|
||||||
|
start_time_difference_in_mins <= 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventStartedLessThanOneHourAgo(const CalendarEvent& event,
|
||||||
|
const base::Time& now_local) {
|
||||||
|
const int start_time_difference_in_mins =
|
||||||
|
(ash::calendar_utils::GetStartTimeAdjusted(&event) - now_local)
|
||||||
|
.InMinutes();
|
||||||
|
const int end_time_difference_in_mins =
|
||||||
|
(ash::calendar_utils::GetEndTimeAdjusted(&event) - now_local).InMinutes();
|
||||||
|
|
||||||
|
return (0 <= end_time_difference_in_mins &&
|
||||||
|
0 > start_time_difference_in_mins &&
|
||||||
|
start_time_difference_in_mins >= -60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns events that start in 10 minutes time, or events that are in progress
|
||||||
|
// and started less than one hour ago.
|
||||||
|
auto FilterEventsStartingSoonOrRecentlyInProgress(
|
||||||
|
const ash::SingleDayEventList& list,
|
||||||
|
const base::Time& now_local) {
|
||||||
|
std::list<CalendarEvent> result;
|
||||||
|
|
||||||
|
for (const CalendarEvent& event : list) {
|
||||||
|
if (EventStartsInTenMins(event, now_local) ||
|
||||||
|
EventStartedLessThanOneHourAgo(event, now_local))
|
||||||
|
result.emplace_back(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace ash {
|
namespace ash {
|
||||||
@@ -479,6 +518,12 @@ CalendarModel::FindEventsSplitByMultiDayAndSameDay(base::Time day) const {
|
|||||||
return SplitEventsIntoMultiDayAndSameDay(FindEvents(day));
|
return SplitEventsIntoMultiDayAndSameDay(FindEvents(day));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::list<CalendarEvent> CalendarModel::FindUpcomingEvents(
|
||||||
|
base::Time now_local) const {
|
||||||
|
return FilterEventsStartingSoonOrRecentlyInProgress(FindEvents(now_local),
|
||||||
|
now_local);
|
||||||
|
}
|
||||||
|
|
||||||
CalendarModel::FetchingStatus CalendarModel::FindFetchingStatus(
|
CalendarModel::FetchingStatus CalendarModel::FindFetchingStatus(
|
||||||
base::Time start_time) const {
|
base::Time start_time) const {
|
||||||
if (!calendar_utils::ShouldFetchEvents())
|
if (!calendar_utils::ShouldFetchEvents())
|
||||||
|
@@ -121,6 +121,11 @@ class ASH_EXPORT CalendarModel : public SessionObserver {
|
|||||||
std::tuple<SingleDayEventList, SingleDayEventList>
|
std::tuple<SingleDayEventList, SingleDayEventList>
|
||||||
FindEventsSplitByMultiDayAndSameDay(base::Time day) const;
|
FindEventsSplitByMultiDayAndSameDay(base::Time day) const;
|
||||||
|
|
||||||
|
// Uses the `FindEvents` method to get events for that day and then filters
|
||||||
|
// the result into events that start or end in the next two hours.
|
||||||
|
std::list<google_apis::calendar::CalendarEvent> FindUpcomingEvents(
|
||||||
|
base::Time now_local) const;
|
||||||
|
|
||||||
// Checks the `FetchingStatus` of a given start time.
|
// Checks the `FetchingStatus` of a given start time.
|
||||||
FetchingStatus FindFetchingStatus(base::Time start_time) const;
|
FetchingStatus FindFetchingStatus(base::Time start_time) const;
|
||||||
|
|
||||||
@@ -139,6 +144,8 @@ class ASH_EXPORT CalendarModel : public SessionObserver {
|
|||||||
friend class CalendarViewAnimationTest;
|
friend class CalendarViewAnimationTest;
|
||||||
friend class CalendarViewEventListViewTest;
|
friend class CalendarViewEventListViewTest;
|
||||||
friend class CalendarViewTest;
|
friend class CalendarViewTest;
|
||||||
|
friend class CalendarViewWithJellyEnabledTest;
|
||||||
|
friend class CalendarUpNextViewTest;
|
||||||
friend class GlanceablesTest;
|
friend class GlanceablesTest;
|
||||||
|
|
||||||
// Checks if the event has allowed statuses and is eligible for insertion.
|
// Checks if the event has allowed statuses and is eligible for insertion.
|
||||||
|
@@ -1243,4 +1243,78 @@ TEST_F(CalendarModelTest, FindEventsSplitByMultiDayAndSameDay) {
|
|||||||
EXPECT_EQ(same_day_events.back().id(), kSameDayId);
|
EXPECT_EQ(same_day_events.back().id(), kSameDayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CalendarModelTest, FindUpcomingEvents) {
|
||||||
|
// Set timezone and fake now.
|
||||||
|
const char* kNow = "10 Nov 2022 13:00 GMT";
|
||||||
|
ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
|
||||||
|
SetTodayFromStr(kNow);
|
||||||
|
|
||||||
|
const char* kSummary = "summary";
|
||||||
|
const char* kEventStartingInTenMinsId = "event_starting_in_ten_mins";
|
||||||
|
const char* kEventStartingInThirtyMinsId = "event_starting_in_thirty_mins";
|
||||||
|
const char* kEventStartingInTwoHoursId = "event_starting_in_two_hours";
|
||||||
|
const char* kEventInProgressStartedLessThanOneHourAgoId =
|
||||||
|
"event_in_progress_started_less_than_one_hour_ago";
|
||||||
|
const char* kEventInProgressStartedMoreThanOneHourAgoId =
|
||||||
|
"event_in_progress_started_more_than_one_hour_ago";
|
||||||
|
const char* kEventFinishedId = "event_finished";
|
||||||
|
|
||||||
|
auto event_starting_in_ten_mins = calendar_test_utils::CreateEvent(
|
||||||
|
kEventStartingInTenMinsId, kSummary, "10 Nov 2022 13:10 GMT",
|
||||||
|
"10 Nov 2022 15:00 GMT");
|
||||||
|
auto event_starting_in_thirty_mins = calendar_test_utils::CreateEvent(
|
||||||
|
kEventStartingInThirtyMinsId, kSummary, "10 Nov 2022 13:30 GMT",
|
||||||
|
"10 Nov 2022 15:00 GMT");
|
||||||
|
auto event_starting_in_two_hours = calendar_test_utils::CreateEvent(
|
||||||
|
kEventStartingInTwoHoursId, kSummary, "10 Nov 2022 15:00 GMT",
|
||||||
|
"10 Nov 2022 16:00 GMT");
|
||||||
|
auto event_in_progress_started_less_than_one_hour_ago =
|
||||||
|
calendar_test_utils::CreateEvent(
|
||||||
|
kEventInProgressStartedLessThanOneHourAgoId, kSummary,
|
||||||
|
"10 Nov 2022 12:01:00 GMT", "10 Nov 2022 17:00 GMT");
|
||||||
|
auto event_in_progress_started_more_than_one_hour_ago =
|
||||||
|
calendar_test_utils::CreateEvent(
|
||||||
|
kEventInProgressStartedMoreThanOneHourAgoId, kSummary,
|
||||||
|
"10 Nov 2022 11:00 GMT", "10 Nov 2022 17:00 GMT");
|
||||||
|
auto event_finished = calendar_test_utils::CreateEvent(
|
||||||
|
kEventFinishedId, kSummary, "10 Nov 2022 12:30 GMT",
|
||||||
|
"10 Nov 2022 12:59 GMT");
|
||||||
|
|
||||||
|
// Prepare mock events list.
|
||||||
|
std::unique_ptr<google_apis::calendar::EventList> event_list =
|
||||||
|
std::make_unique<google_apis::calendar::EventList>();
|
||||||
|
event_list->InjectItemForTesting(std::move(event_starting_in_ten_mins));
|
||||||
|
event_list->InjectItemForTesting(std::move(event_starting_in_thirty_mins));
|
||||||
|
event_list->InjectItemForTesting(std::move(event_starting_in_two_hours));
|
||||||
|
event_list->InjectItemForTesting(
|
||||||
|
std::move(event_in_progress_started_less_than_one_hour_ago));
|
||||||
|
event_list->InjectItemForTesting(
|
||||||
|
std::move(event_in_progress_started_more_than_one_hour_ago));
|
||||||
|
event_list->InjectItemForTesting(std::move(event_finished));
|
||||||
|
|
||||||
|
// Mock the events are fetched.
|
||||||
|
MockOnEventsFetched(calendar_utils::GetStartOfMonthUTC(
|
||||||
|
calendar_test_utils::GetTimeFromString(kNow)),
|
||||||
|
google_apis::ApiErrorCode::HTTP_SUCCESS,
|
||||||
|
event_list.get());
|
||||||
|
|
||||||
|
auto events = calendar_model_->FindUpcomingEvents(now_);
|
||||||
|
|
||||||
|
auto event_list_contains = [](auto& event_list, auto& id) {
|
||||||
|
return base::Contains(event_list, id, &CalendarEvent::id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// We should only get the 2 events back that start in 10 mins or were ongoing
|
||||||
|
// with < 60 mins passed.
|
||||||
|
EXPECT_EQ(events.size(), size_t(2));
|
||||||
|
EXPECT_TRUE(event_list_contains(events, kEventStartingInTenMinsId));
|
||||||
|
EXPECT_FALSE(event_list_contains(events, kEventStartingInThirtyMinsId));
|
||||||
|
EXPECT_FALSE(event_list_contains(events, kEventStartingInTwoHoursId));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
event_list_contains(events, kEventInProgressStartedLessThanOneHourAgoId));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
event_list_contains(events, kEventInProgressStartedMoreThanOneHourAgoId));
|
||||||
|
EXPECT_FALSE(event_list_contains(events, kEventFinishedId));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ash
|
} // namespace ash
|
||||||
|
113
ash/system/time/calendar_up_next_view.cc
Normal file
113
ash/system/time/calendar_up_next_view.cc
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2022 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/time/calendar_up_next_view.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "ash/bubble/bubble_utils.h"
|
||||||
|
#include "ash/strings/grit/ash_strings.h"
|
||||||
|
#include "ash/system/time/calendar_event_list_item_view_jelly.h"
|
||||||
|
#include "ui/base/l10n/l10n_util.h"
|
||||||
|
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||||
|
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
|
||||||
|
#include "ui/color/color_provider.h"
|
||||||
|
#include "ui/views/background.h"
|
||||||
|
#include "ui/views/controls/label.h"
|
||||||
|
#include "ui/views/controls/scroll_view.h"
|
||||||
|
#include "ui/views/layout/box_layout.h"
|
||||||
|
#include "ui/views/layout/flex_layout.h"
|
||||||
|
#include "ui/views/metadata/view_factory_internal.h"
|
||||||
|
|
||||||
|
namespace ash {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kContainerInsets = 12;
|
||||||
|
constexpr int kBackgroundRadius = 12;
|
||||||
|
constexpr int kBetweenChildSpacing = 8;
|
||||||
|
constexpr int kMaxEventListItemWidth = 160;
|
||||||
|
|
||||||
|
views::Builder<views::Label> CreateHeaderLabel() {
|
||||||
|
return views::Builder<views::Label>(bubble_utils::CreateLabel(
|
||||||
|
bubble_utils::TypographyStyle::kButton2,
|
||||||
|
l10n_util::GetStringUTF16(IDS_ASH_CALENDAR_UP_NEXT)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CalendarUpNextView::CalendarUpNextView(
|
||||||
|
CalendarViewController* calendar_view_controller)
|
||||||
|
: calendar_view_controller_(calendar_view_controller),
|
||||||
|
header_view_(AddChildView(std::make_unique<views::View>())),
|
||||||
|
scroll_view_(AddChildView(std::make_unique<views::ScrollView>(
|
||||||
|
views::ScrollView::ScrollWithLayers::kEnabled))),
|
||||||
|
content_view_(
|
||||||
|
scroll_view_->SetContents(std::make_unique<views::View>())) {
|
||||||
|
SetLayoutManager(std::make_unique<views::BoxLayout>(
|
||||||
|
views::BoxLayout::Orientation::kVertical, gfx::Insets(kContainerInsets),
|
||||||
|
kBetweenChildSpacing));
|
||||||
|
|
||||||
|
header_view_->SetLayoutManager(std::make_unique<views::FlexLayout>());
|
||||||
|
header_view_->AddChildView(CreateHeaderLabel().Build());
|
||||||
|
|
||||||
|
scroll_view_->SetAllowKeyboardScrolling(false);
|
||||||
|
scroll_view_->SetBackgroundColor(absl::nullopt);
|
||||||
|
scroll_view_->SetDrawOverflowIndicator(false);
|
||||||
|
scroll_view_->SetHorizontalScrollBarMode(
|
||||||
|
views::ScrollView::ScrollBarMode::kHiddenButEnabled);
|
||||||
|
scroll_view_->SetTreatAllScrollEventsAsHorizontal(true);
|
||||||
|
|
||||||
|
content_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
|
||||||
|
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
|
||||||
|
kBetweenChildSpacing));
|
||||||
|
|
||||||
|
UpdateEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
CalendarUpNextView::~CalendarUpNextView() = default;
|
||||||
|
|
||||||
|
void CalendarUpNextView::Layout() {
|
||||||
|
// For some reason the `content_view_` is constrained to the `scroll_view_`
|
||||||
|
// width and so it isn't scrollable. This seems to be a problem with
|
||||||
|
// horizontal `ScrollView`s as this doesn't happen if you make this view
|
||||||
|
// vertically scrollable. To make the content scrollable, we need to set it's
|
||||||
|
// preferred size here so it's bigger than the `scroll_view_` and
|
||||||
|
// therefore scrolls.
|
||||||
|
if (content_view_)
|
||||||
|
content_view_->SizeToPreferredSize();
|
||||||
|
|
||||||
|
// `content_view_` is a child of this class so we need to Layout after
|
||||||
|
// changing its width.
|
||||||
|
views::View::Layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalendarUpNextView::OnThemeChanged() {
|
||||||
|
views::View::OnThemeChanged();
|
||||||
|
SetBackground(views::CreateRoundedRectBackground(
|
||||||
|
GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemOnBase),
|
||||||
|
kBackgroundRadius));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalendarUpNextView::UpdateEvents() {
|
||||||
|
content_view_->RemoveAllChildViews();
|
||||||
|
|
||||||
|
std::list<google_apis::calendar::CalendarEvent> events =
|
||||||
|
calendar_view_controller_->UpcomingEvents();
|
||||||
|
|
||||||
|
auto now = base::Time::NowFromSystemTime();
|
||||||
|
for (auto& event : events) {
|
||||||
|
content_view_->AddChildView(
|
||||||
|
std::make_unique<CalendarEventListItemViewJelly>(
|
||||||
|
calendar_view_controller_,
|
||||||
|
SelectedDateParams{now, now.UTCMidnight(), now.LocalMidnight()},
|
||||||
|
/*event=*/event, /*round_top_corners=*/true,
|
||||||
|
/*round_bottom_corners=*/true,
|
||||||
|
/*max_width=*/kMaxEventListItemWidth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN_METADATA(CalendarUpNextView, views::View);
|
||||||
|
END_METADATA
|
||||||
|
|
||||||
|
} // namespace ash
|
48
ash/system/time/calendar_up_next_view.h
Normal file
48
ash/system/time/calendar_up_next_view.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2022 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_SYSTEM_TIME_CALENDAR_UP_NEXT_VIEW_H_
|
||||||
|
#define ASH_SYSTEM_TIME_CALENDAR_UP_NEXT_VIEW_H_
|
||||||
|
|
||||||
|
#include "ash/ash_export.h"
|
||||||
|
#include "ash/system/time/calendar_view_controller.h"
|
||||||
|
#include "ui/views/view.h"
|
||||||
|
|
||||||
|
namespace ash {
|
||||||
|
|
||||||
|
// This view displays a scrollable list of `CalendarEventListItemView` for the
|
||||||
|
// events that a user has coming up, either imminently or that are already in
|
||||||
|
// progress but not yet finished.
|
||||||
|
class ASH_EXPORT CalendarUpNextView : public views::View {
|
||||||
|
public:
|
||||||
|
METADATA_HEADER(CalendarUpNextView);
|
||||||
|
|
||||||
|
explicit CalendarUpNextView(CalendarViewController* calendar_view_controller);
|
||||||
|
CalendarUpNextView(const CalendarUpNextView& other) = delete;
|
||||||
|
CalendarUpNextView& operator=(const CalendarUpNextView& other) = delete;
|
||||||
|
~CalendarUpNextView() override;
|
||||||
|
|
||||||
|
// views::View
|
||||||
|
void OnThemeChanged() override;
|
||||||
|
void Layout() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class CalendarUpNextViewTest;
|
||||||
|
|
||||||
|
void UpdateEvents();
|
||||||
|
|
||||||
|
// Owned by `CalendarView`.
|
||||||
|
CalendarViewController* calendar_view_controller_;
|
||||||
|
|
||||||
|
// Owned by `CalendarUpNextView`.
|
||||||
|
views::View* const header_view_;
|
||||||
|
views::ScrollView* const scroll_view_;
|
||||||
|
// The content of the horizontal `scroll_view`, which carries a list of
|
||||||
|
// `CalendarEventListItemView`.
|
||||||
|
views::View* const content_view_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ash
|
||||||
|
|
||||||
|
#endif // ASH_SYSTEM_TIME_CALENDAR_UP_NEXT_VIEW_H_
|
129
ash/system/time/calendar_up_next_view_unittest.cc
Normal file
129
ash/system/time/calendar_up_next_view_unittest.cc
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2022 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/time/calendar_up_next_view.h"
|
||||||
|
|
||||||
|
#include "ash/shell.h"
|
||||||
|
#include "ash/system/model/system_tray_model.h"
|
||||||
|
#include "ash/system/time/calendar_unittest_utils.h"
|
||||||
|
#include "ash/system/time/calendar_view_controller.h"
|
||||||
|
#include "ash/test/ash_test_base.h"
|
||||||
|
#include "base/test/scoped_feature_list.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "ui/views/controls/label.h"
|
||||||
|
|
||||||
|
namespace ash {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::unique_ptr<google_apis::calendar::CalendarEvent> CreateEvent(
|
||||||
|
const char* start_time,
|
||||||
|
const char* end_time,
|
||||||
|
bool all_day_event = false) {
|
||||||
|
return calendar_test_utils::CreateEvent(
|
||||||
|
"id_0", "summary_0", start_time, end_time,
|
||||||
|
google_apis::calendar::CalendarEvent::EventStatus::kConfirmed,
|
||||||
|
google_apis::calendar::CalendarEvent::ResponseStatus::kAccepted,
|
||||||
|
all_day_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<google_apis::calendar::EventList> CreateMockEventList(
|
||||||
|
std::list<std::unique_ptr<google_apis::calendar::CalendarEvent>>& events) {
|
||||||
|
auto event_list = std::make_unique<google_apis::calendar::EventList>();
|
||||||
|
event_list->set_time_zone("Greenwich Mean Time");
|
||||||
|
|
||||||
|
for (auto& event : events)
|
||||||
|
event_list->InjectItemForTesting(std::move(event));
|
||||||
|
|
||||||
|
return event_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class CalendarUpNextViewTest : public AshTestBase {
|
||||||
|
public:
|
||||||
|
CalendarUpNextViewTest() = default;
|
||||||
|
CalendarUpNextViewTest(const CalendarUpNextViewTest&) = delete;
|
||||||
|
CalendarUpNextViewTest& operator=(const CalendarUpNextViewTest&) = delete;
|
||||||
|
~CalendarUpNextViewTest() override = default;
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
AshTestBase::SetUp();
|
||||||
|
controller_ = std::make_unique<CalendarViewController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
up_next_view_.reset();
|
||||||
|
controller_.reset();
|
||||||
|
AshTestBase::TearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateUpNextView(
|
||||||
|
base::Time date,
|
||||||
|
std::list<std::unique_ptr<google_apis::calendar::CalendarEvent>>&
|
||||||
|
events) {
|
||||||
|
up_next_view_.reset();
|
||||||
|
Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
|
||||||
|
calendar_utils::GetStartOfMonthUTC(date),
|
||||||
|
google_apis::ApiErrorCode::HTTP_SUCCESS,
|
||||||
|
CreateMockEventList(events).get());
|
||||||
|
up_next_view_ = std::make_unique<CalendarUpNextView>(controller_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
const views::Label* GetHeaderLabel() {
|
||||||
|
return static_cast<views::Label*>(
|
||||||
|
up_next_view_->header_view_->children()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const views::View* GetContentsView() {
|
||||||
|
return static_cast<views::View*>(up_next_view_->content_view_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static base::Time FakeTimeNow() { return fake_time_; }
|
||||||
|
static void SetFakeNow(base::Time fake_now) { fake_time_ = fake_now; }
|
||||||
|
|
||||||
|
CalendarViewController* controller() { return controller_.get(); }
|
||||||
|
|
||||||
|
CalendarUpNextView* up_next_view() { return up_next_view_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<CalendarUpNextView> up_next_view_;
|
||||||
|
std::unique_ptr<CalendarViewController> controller_;
|
||||||
|
base::test::ScopedFeatureList features_;
|
||||||
|
static base::Time fake_time_;
|
||||||
|
};
|
||||||
|
|
||||||
|
base::Time CalendarUpNextViewTest::fake_time_;
|
||||||
|
|
||||||
|
TEST_F(CalendarUpNextViewTest,
|
||||||
|
GivenUpcomingEvents_WhenUpNextViewIsCreated_ThenShowEvents) {
|
||||||
|
base::Time date;
|
||||||
|
ASSERT_TRUE(base::Time::FromString("22 Nov 2021 09:00 GMT", &date));
|
||||||
|
|
||||||
|
// Set time override.
|
||||||
|
SetFakeNow(date);
|
||||||
|
base::subtle::ScopedTimeClockOverrides time_override(
|
||||||
|
&CalendarUpNextViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
|
||||||
|
/*thread_ticks_override=*/nullptr);
|
||||||
|
|
||||||
|
// Event starts in 10 mins.
|
||||||
|
const char* event_in_ten_mins_start_time_string = "22 Nov 2021 09:10 GMT";
|
||||||
|
const char* event_in_ten_mins_end_time_string = "22 Nov 2021 10:00 GMT";
|
||||||
|
// Event in progress.
|
||||||
|
const char* event_in_progress_start_time_string = "22 Nov 2021 08:30 GMT";
|
||||||
|
const char* event_in_progress_end_time_string = "22 Nov 2021 09:30 GMT";
|
||||||
|
|
||||||
|
std::list<std::unique_ptr<google_apis::calendar::CalendarEvent>> events;
|
||||||
|
events.emplace_back(CreateEvent(event_in_ten_mins_start_time_string,
|
||||||
|
event_in_ten_mins_end_time_string));
|
||||||
|
events.emplace_back(CreateEvent(event_in_progress_start_time_string,
|
||||||
|
event_in_progress_end_time_string));
|
||||||
|
|
||||||
|
CreateUpNextView(date, events);
|
||||||
|
|
||||||
|
EXPECT_EQ(GetHeaderLabel()->GetText(), u"Up next");
|
||||||
|
EXPECT_EQ(GetContentsView()->children().size(), size_t(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ash
|
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "ash/constants/ash_features.h"
|
||||||
#include "ash/public/cpp/ash_typography.h"
|
#include "ash/public/cpp/ash_typography.h"
|
||||||
#include "ash/resources/vector_icons/vector_icons.h"
|
#include "ash/resources/vector_icons/vector_icons.h"
|
||||||
#include "ash/strings/grit/ash_strings.h"
|
#include "ash/strings/grit/ash_strings.h"
|
||||||
@@ -89,6 +90,9 @@ constexpr base::TimeDelta kAnimationDurationForClosingEvents =
|
|||||||
// The cool-down time for enabling animation.
|
// The cool-down time for enabling animation.
|
||||||
constexpr base::TimeDelta kAnimationDisablingTimeout = base::Milliseconds(500);
|
constexpr base::TimeDelta kAnimationDisablingTimeout = base::Milliseconds(500);
|
||||||
|
|
||||||
|
// Periodic time delay for checking upcoming events.
|
||||||
|
constexpr base::TimeDelta kCheckUpcomingEventsDelay = base::Seconds(15);
|
||||||
|
|
||||||
// The multiplier used to reduce velocity of flings on the calendar view.
|
// The multiplier used to reduce velocity of flings on the calendar view.
|
||||||
// Without this, CalendarView will scroll a few years per fast swipe.
|
// Without this, CalendarView will scroll a few years per fast swipe.
|
||||||
constexpr float kCalendarScrollFlingMultiplier = 0.25f;
|
constexpr float kCalendarScrollFlingMultiplier = 0.25f;
|
||||||
@@ -371,6 +375,8 @@ CalendarView::CalendarView(DetailedViewDelegate* delegate,
|
|||||||
calendar_view->set_should_months_animate(true);
|
calendar_view->set_should_months_animate(true);
|
||||||
},
|
},
|
||||||
base::Unretained(this))) {
|
base::Unretained(this))) {
|
||||||
|
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
|
||||||
|
views::BoxLayout::Orientation::kVertical));
|
||||||
SetFocusBehavior(FocusBehavior::ALWAYS);
|
SetFocusBehavior(FocusBehavior::ALWAYS);
|
||||||
|
|
||||||
// Focusable nodes must have an accessible name and valid role.
|
// Focusable nodes must have an accessible name and valid role.
|
||||||
@@ -443,6 +449,8 @@ CalendarView::CalendarView(DetailedViewDelegate* delegate,
|
|||||||
|
|
||||||
// Add scroll view.
|
// Add scroll view.
|
||||||
scroll_view_ = AddChildView(std::make_unique<views::ScrollView>());
|
scroll_view_ = AddChildView(std::make_unique<views::ScrollView>());
|
||||||
|
// Flex the scrollview around any sibling views that are added or removed.
|
||||||
|
layout->SetFlexForView(scroll_view_, 1);
|
||||||
scroll_view_->SetAllowKeyboardScrolling(false);
|
scroll_view_->SetAllowKeyboardScrolling(false);
|
||||||
scroll_view_->SetBackgroundColor(absl::nullopt);
|
scroll_view_->SetBackgroundColor(absl::nullopt);
|
||||||
scroll_view_->ClipHeightTo(0, INT_MAX);
|
scroll_view_->ClipHeightTo(0, INT_MAX);
|
||||||
@@ -483,6 +491,11 @@ CalendarView::CalendarView(DetailedViewDelegate* delegate,
|
|||||||
scoped_view_observer_.AddObservation(scroll_view_);
|
scoped_view_observer_.AddObservation(scroll_view_);
|
||||||
scoped_view_observer_.AddObservation(content_view_);
|
scoped_view_observer_.AddObservation(content_view_);
|
||||||
scoped_view_observer_.AddObservation(this);
|
scoped_view_observer_.AddObservation(this);
|
||||||
|
|
||||||
|
check_upcoming_events_timer_.Start(
|
||||||
|
FROM_HERE, kCheckUpcomingEventsDelay,
|
||||||
|
base::BindRepeating(&CalendarView::MaybeShowUpNextView,
|
||||||
|
base::Unretained(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
CalendarView::~CalendarView() {
|
CalendarView::~CalendarView() {
|
||||||
@@ -497,6 +510,8 @@ CalendarView::~CalendarView() {
|
|||||||
RemoveChildViewT(event_list_view_);
|
RemoveChildViewT(event_list_view_);
|
||||||
event_list_view_ = nullptr;
|
event_list_view_ = nullptr;
|
||||||
}
|
}
|
||||||
|
check_upcoming_events_timer_.Stop();
|
||||||
|
RemoveUpNextView();
|
||||||
content_view_->RemoveAllChildViews();
|
content_view_->RemoveAllChildViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,17 +727,20 @@ void CalendarView::UpdateOnScreenMonthMap() {
|
|||||||
MaybeUpdateLoadingBarVisibility();
|
MaybeUpdateLoadingBarVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalendarView::MaybeUpdateLoadingBarVisibility() {
|
bool CalendarView::EventsFetchComplete() {
|
||||||
for (auto& it : on_screen_month_) {
|
for (auto& it : on_screen_month_) {
|
||||||
// If there's an on-screen month that hasn't finished fetching or
|
// Return false if there's an on-screen month that hasn't finished fetching
|
||||||
// re-fetching, the loading bar should be visible.
|
// or re-fetching.
|
||||||
if (it.second == CalendarModel::kFetching ||
|
if (it.second == CalendarModel::kFetching ||
|
||||||
it.second == CalendarModel::kRefetching) {
|
it.second == CalendarModel::kRefetching) {
|
||||||
ShowProgress(-1, true);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ShowProgress(-1, false);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalendarView::MaybeUpdateLoadingBarVisibility() {
|
||||||
|
ShowProgress(-1, !EventsFetchComplete());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalendarView::FadeInCurrentMonth() {
|
void CalendarView::FadeInCurrentMonth() {
|
||||||
@@ -995,6 +1013,11 @@ void CalendarView::OnEventsFetched(
|
|||||||
on_screen_month_[start_time] = status;
|
on_screen_month_[start_time] = status;
|
||||||
|
|
||||||
MaybeUpdateLoadingBarVisibility();
|
MaybeUpdateLoadingBarVisibility();
|
||||||
|
|
||||||
|
// Only show up next for events that are the same month as `base::Time::Now`.
|
||||||
|
if (start_time == calendar_utils::GetStartOfMonthUTC(
|
||||||
|
base::Time::NowFromSystemTime().UTCMidnight()))
|
||||||
|
MaybeShowUpNextView();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalendarView::OnTimeout(const base::Time start_time) {
|
void CalendarView::OnTimeout(const base::Time start_time) {
|
||||||
@@ -1799,6 +1822,30 @@ void CalendarView::SetEventListViewBounds() {
|
|||||||
kEventListViewVerticalPadding);
|
kEventListViewVerticalPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CalendarView::MaybeShowUpNextView() {
|
||||||
|
if (features::IsCalendarJellyEnabled() && EventsFetchComplete() &&
|
||||||
|
!calendar_view_controller_->UpcomingEvents().empty()) {
|
||||||
|
if (up_next_view_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
up_next_view_ = AddChildView(
|
||||||
|
std::make_unique<CalendarUpNextView>(calendar_view_controller_.get()));
|
||||||
|
} else {
|
||||||
|
RemoveUpNextView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalendarView::RemoveUpNextView() {
|
||||||
|
if (up_next_view_) {
|
||||||
|
RemoveChildViewT(up_next_view_);
|
||||||
|
up_next_view_ = nullptr;
|
||||||
|
// If the up next view is deleted whilst the calendar is still open, e.g.
|
||||||
|
// time has passed and an event no longer meets 'upcoming' criteria, then
|
||||||
|
// the calendar view needs to relayout after removing the upnext view.
|
||||||
|
InvalidateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BEGIN_METADATA(CalendarView, views::View)
|
BEGIN_METADATA(CalendarView, views::View)
|
||||||
END_METADATA
|
END_METADATA
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
#include "ash/strings/grit/ash_strings.h"
|
#include "ash/strings/grit/ash_strings.h"
|
||||||
#include "ash/system/model/system_tray_model.h"
|
#include "ash/system/model/system_tray_model.h"
|
||||||
#include "ash/system/time/calendar_model.h"
|
#include "ash/system/time/calendar_model.h"
|
||||||
|
#include "ash/system/time/calendar_up_next_view.h"
|
||||||
#include "ash/system/time/calendar_view_controller.h"
|
#include "ash/system/time/calendar_view_controller.h"
|
||||||
#include "ash/system/tray/tray_detailed_view.h"
|
#include "ash/system/tray/tray_detailed_view.h"
|
||||||
#include "ash/system/unified/unified_system_tray_controller.h"
|
#include "ash/system/unified/unified_system_tray_controller.h"
|
||||||
@@ -231,6 +232,9 @@ class ASH_EXPORT CalendarView : public CalendarModel::Observer,
|
|||||||
// Updates the on-screen month map with the current months on screen.
|
// Updates the on-screen month map with the current months on screen.
|
||||||
void UpdateOnScreenMonthMap();
|
void UpdateOnScreenMonthMap();
|
||||||
|
|
||||||
|
// Returns whether or not we've finished fetching CalendarEvents.
|
||||||
|
bool EventsFetchComplete();
|
||||||
|
|
||||||
// Checks if all months in the visible window have finished fetching. If so,
|
// Checks if all months in the visible window have finished fetching. If so,
|
||||||
// stop showing the loading bar.
|
// stop showing the loading bar.
|
||||||
void MaybeUpdateLoadingBarVisibility();
|
void MaybeUpdateLoadingBarVisibility();
|
||||||
@@ -324,6 +328,12 @@ class ASH_EXPORT CalendarView : public CalendarModel::Observer,
|
|||||||
// bounds.
|
// bounds.
|
||||||
void SetEventListViewBounds();
|
void SetEventListViewBounds();
|
||||||
|
|
||||||
|
// Conditionally displays the "Up next" view.
|
||||||
|
void MaybeShowUpNextView();
|
||||||
|
|
||||||
|
// Removes the "Up next" view.
|
||||||
|
void RemoveUpNextView();
|
||||||
|
|
||||||
// Setters for animation flags.
|
// Setters for animation flags.
|
||||||
void set_should_header_animate(bool should_animate) {
|
void set_should_header_animate(bool should_animate) {
|
||||||
should_header_animate_ = should_animate;
|
should_header_animate_ = should_animate;
|
||||||
@@ -362,6 +372,7 @@ class ASH_EXPORT CalendarView : public CalendarModel::Observer,
|
|||||||
IconButton* up_button_ = nullptr;
|
IconButton* up_button_ = nullptr;
|
||||||
IconButton* down_button_ = nullptr;
|
IconButton* down_button_ = nullptr;
|
||||||
CalendarEventListView* event_list_view_ = nullptr;
|
CalendarEventListView* event_list_view_ = nullptr;
|
||||||
|
CalendarUpNextView* up_next_view_ = nullptr;
|
||||||
std::map<base::Time, CalendarModel::FetchingStatus> on_screen_month_;
|
std::map<base::Time, CalendarModel::FetchingStatus> on_screen_month_;
|
||||||
CalendarModel* calendar_model_ =
|
CalendarModel* calendar_model_ =
|
||||||
Shell::Get()->system_tray_model()->calendar_model();
|
Shell::Get()->system_tray_model()->calendar_model();
|
||||||
@@ -399,6 +410,9 @@ class ASH_EXPORT CalendarView : public CalendarModel::Observer,
|
|||||||
base::RetainingOneShotTimer header_animation_restart_timer_;
|
base::RetainingOneShotTimer header_animation_restart_timer_;
|
||||||
base::RetainingOneShotTimer months_animation_restart_timer_;
|
base::RetainingOneShotTimer months_animation_restart_timer_;
|
||||||
|
|
||||||
|
// Timer that checks upcoming events periodically.
|
||||||
|
base::RepeatingTimer check_upcoming_events_timer_;
|
||||||
|
|
||||||
base::CallbackListSubscription on_contents_scrolled_subscription_;
|
base::CallbackListSubscription on_contents_scrolled_subscription_;
|
||||||
base::ScopedObservation<CalendarModel, CalendarModel::Observer>
|
base::ScopedObservation<CalendarModel, CalendarModel::Observer>
|
||||||
scoped_calendar_model_observer_{this};
|
scoped_calendar_model_observer_{this};
|
||||||
|
@@ -158,6 +158,14 @@ CalendarViewController::SelectedDateEventsSplitByMultiDayAndSameDay() {
|
|||||||
ApplyTimeDifference(selected_date_.value()));
|
ApplyTimeDifference(selected_date_.value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SingleDayEventList CalendarViewController::UpcomingEvents() {
|
||||||
|
return Shell::Get()
|
||||||
|
->system_tray_model()
|
||||||
|
->calendar_model()
|
||||||
|
->FindUpcomingEvents(
|
||||||
|
ApplyTimeDifference(base::Time::NowFromSystemTime()));
|
||||||
|
}
|
||||||
|
|
||||||
int CalendarViewController::GetEventNumber(base::Time date) {
|
int CalendarViewController::GetEventNumber(base::Time date) {
|
||||||
return Shell::Get()->system_tray_model()->calendar_model()->EventsNumberOfDay(
|
return Shell::Get()->system_tray_model()->calendar_model()->EventsNumberOfDay(
|
||||||
ApplyTimeDifference(date),
|
ApplyTimeDifference(date),
|
||||||
|
@@ -116,6 +116,9 @@ class ASH_EXPORT CalendarViewController {
|
|||||||
std::tuple<SingleDayEventList, SingleDayEventList>
|
std::tuple<SingleDayEventList, SingleDayEventList>
|
||||||
SelectedDateEventsSplitByMultiDayAndSameDay();
|
SelectedDateEventsSplitByMultiDayAndSameDay();
|
||||||
|
|
||||||
|
// Returns upcoming events for the "Up next" view.
|
||||||
|
SingleDayEventList UpcomingEvents();
|
||||||
|
|
||||||
// The calendar events number of the `date`.
|
// The calendar events number of the `date`.
|
||||||
int GetEventNumber(base::Time date);
|
int GetEventNumber(base::Time date);
|
||||||
|
|
||||||
|
@@ -189,6 +189,8 @@ class CalendarViewTest : public AshTestBase {
|
|||||||
}
|
}
|
||||||
views::View* event_list_view() { return calendar_view_->event_list_view_; }
|
views::View* event_list_view() { return calendar_view_->event_list_view_; }
|
||||||
|
|
||||||
|
views::View* up_next_view() { return calendar_view_->up_next_view_; }
|
||||||
|
|
||||||
void ScrollUpOneMonth() {
|
void ScrollUpOneMonth() {
|
||||||
calendar_view_->ScrollOneMonthAndAutoScroll(/*scroll_up=*/true);
|
calendar_view_->ScrollOneMonthAndAutoScroll(/*scroll_up=*/true);
|
||||||
}
|
}
|
||||||
@@ -2142,4 +2144,144 @@ TEST_F(CalendarViewWithMessageCenterTest,
|
|||||||
EXPECT_EQ(current_date_cell_view, calendar_focus_manager()->GetFocusedView());
|
EXPECT_EQ(current_date_cell_view, calendar_focus_manager()->GetFocusedView());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CalendarViewWithJellyEnabledTest : public CalendarViewTest {
|
||||||
|
public:
|
||||||
|
CalendarViewWithJellyEnabledTest() = default;
|
||||||
|
CalendarViewWithJellyEnabledTest(const CalendarViewWithJellyEnabledTest&) =
|
||||||
|
delete;
|
||||||
|
CalendarViewWithJellyEnabledTest& operator=(
|
||||||
|
const CalendarViewWithJellyEnabledTest&) = delete;
|
||||||
|
~CalendarViewWithJellyEnabledTest() override = default;
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
|
||||||
|
scoped_feature_list_->InitWithFeatures(
|
||||||
|
{features::kCalendarView, features::kCalendarJelly}, {});
|
||||||
|
CalendarViewTest::SetUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes current time is "18 Nov 2021 10:00 GMT".
|
||||||
|
std::unique_ptr<google_apis::calendar::EventList>
|
||||||
|
CreateMockEventListWithEventStartTimeMoreThanTwoHoursAway() {
|
||||||
|
auto event_list = std::make_unique<google_apis::calendar::EventList>();
|
||||||
|
event_list->set_time_zone("Greenwich Mean Time");
|
||||||
|
event_list->InjectItemForTesting(calendar_test_utils::CreateEvent(
|
||||||
|
"id_0", "summary_0", "18 Nov 2021 12:30 GMT", "18 Nov 2021 13:30 GMT"));
|
||||||
|
|
||||||
|
return event_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes current time is "18 Nov 2021 10:00 GMT".
|
||||||
|
std::unique_ptr<google_apis::calendar::EventList>
|
||||||
|
CreateMockEventListWithEventStartTimeTenMinsAway() {
|
||||||
|
auto event_list = std::make_unique<google_apis::calendar::EventList>();
|
||||||
|
event_list->set_time_zone("Greenwich Mean Time");
|
||||||
|
event_list->InjectItemForTesting(calendar_test_utils::CreateEvent(
|
||||||
|
"id_0", "summary_0", "18 Nov 2021 10:10 GMT", "18 Nov 2021 13:30 GMT"));
|
||||||
|
|
||||||
|
return event_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MockEventsFetched(
|
||||||
|
base::Time date,
|
||||||
|
std::unique_ptr<google_apis::calendar::EventList> event_list) {
|
||||||
|
Shell::Get()->system_tray_model()->calendar_model()->OnEventsFetched(
|
||||||
|
calendar_utils::GetStartOfMonthUTC(date),
|
||||||
|
google_apis::ApiErrorCode::HTTP_SUCCESS, event_list.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(CalendarViewWithJellyEnabledTest,
|
||||||
|
GivenNoEvents_WhenCalendarViewOpens_ThenUpNextViewShouldNotBeShown) {
|
||||||
|
base::Time date;
|
||||||
|
ASSERT_TRUE(base::Time::FromString("7 Jun 2021 10:00 GMT", &date));
|
||||||
|
// Set time override.
|
||||||
|
SetFakeNow(date);
|
||||||
|
base::subtle::ScopedTimeClockOverrides time_override(
|
||||||
|
&CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
|
||||||
|
/*thread_ticks_override=*/nullptr);
|
||||||
|
Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
|
||||||
|
ash::prefs::kCalendarIntegrationEnabled, false);
|
||||||
|
CreateCalendarView();
|
||||||
|
|
||||||
|
// When we've just created the calendar view and not fetched any events, then
|
||||||
|
// up next shouldn't have been created.
|
||||||
|
bool is_showing_up_next_view = up_next_view();
|
||||||
|
EXPECT_FALSE(is_showing_up_next_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
CalendarViewWithJellyEnabledTest,
|
||||||
|
GivenEventsStartingMoreThanTwoHoursAway_WhenCalendarViewOpens_ThenUpNextViewShouldNotBeShown) {
|
||||||
|
base::Time date;
|
||||||
|
ASSERT_TRUE(base::Time::FromString("18 Nov 2021 10:00 GMT", &date));
|
||||||
|
// Set time override.
|
||||||
|
SetFakeNow(date);
|
||||||
|
base::subtle::ScopedTimeClockOverrides time_override(
|
||||||
|
&CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
|
||||||
|
/*thread_ticks_override=*/nullptr);
|
||||||
|
|
||||||
|
CreateCalendarView();
|
||||||
|
MockEventsFetched(
|
||||||
|
calendar_utils::GetStartOfMonthUTC(date),
|
||||||
|
CreateMockEventListWithEventStartTimeMoreThanTwoHoursAway());
|
||||||
|
|
||||||
|
// When fetched events are more than two hours away, then up next shouldn't
|
||||||
|
// have been created.
|
||||||
|
bool is_showing_up_next_view = up_next_view();
|
||||||
|
EXPECT_FALSE(is_showing_up_next_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
CalendarViewWithJellyEnabledTest,
|
||||||
|
GivenEventsStartingTenMinsAway_WhenCalendarViewOpens_ThenUpNextViewShouldBeShown) {
|
||||||
|
base::Time date;
|
||||||
|
ASSERT_TRUE(base::Time::FromString("18 Nov 2021 10:00 GMT", &date));
|
||||||
|
// Set time override.
|
||||||
|
SetFakeNow(date);
|
||||||
|
base::subtle::ScopedTimeClockOverrides time_override(
|
||||||
|
&CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
|
||||||
|
/*thread_ticks_override=*/nullptr);
|
||||||
|
|
||||||
|
CreateCalendarView();
|
||||||
|
MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
|
||||||
|
CreateMockEventListWithEventStartTimeTenMinsAway());
|
||||||
|
|
||||||
|
// When fetched events are in the next 10 mins, then up next should have been
|
||||||
|
// created.
|
||||||
|
bool is_showing_up_next_view = up_next_view();
|
||||||
|
EXPECT_TRUE(is_showing_up_next_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
CalendarViewWithJellyEnabledTest,
|
||||||
|
GivenUpNextIsShown_WhenNewEventsMoreThanTwoHoursAwayAreFetched_ThenUpNextViewShouldNotBeShown) {
|
||||||
|
base::Time date;
|
||||||
|
ASSERT_TRUE(base::Time::FromString("18 Nov 2021 10:00 GMT", &date));
|
||||||
|
// Set time override.
|
||||||
|
SetFakeNow(date);
|
||||||
|
base::subtle::ScopedTimeClockOverrides time_override(
|
||||||
|
&CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
|
||||||
|
/*thread_ticks_override=*/nullptr);
|
||||||
|
|
||||||
|
CreateCalendarView();
|
||||||
|
MockEventsFetched(calendar_utils::GetStartOfMonthUTC(date),
|
||||||
|
CreateMockEventListWithEventStartTimeTenMinsAway());
|
||||||
|
|
||||||
|
// When fetched events are in the next 10 mins, then up next should have been
|
||||||
|
// created.
|
||||||
|
EXPECT_TRUE(up_next_view());
|
||||||
|
|
||||||
|
MockEventsFetched(
|
||||||
|
calendar_utils::GetStartOfMonthUTC(date),
|
||||||
|
CreateMockEventListWithEventStartTimeMoreThanTwoHoursAway());
|
||||||
|
|
||||||
|
// When fetched events are now more than two hours away, then up next
|
||||||
|
// should have been destroyed.
|
||||||
|
EXPECT_FALSE(up_next_view());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ash
|
} // namespace ash
|
||||||
|
Reference in New Issue
Block a user