blob: 505e4a325c2c3414e546f79283f250b8b0a90626 [file] [log] [blame]
// 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/glanceables/glanceables_up_next_view.h"
#include <memory>
#include <string>
#include "ash/glanceables/glanceables_up_next_event_item_view.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/calendar_utils.h"
#include "base/time/time.h"
#include "google_apis/calendar/calendar_api_response_types.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/view.h"
namespace ash {
namespace {
using ::google_apis::calendar::CalendarEvent;
bool IsAllDayEvent(const CalendarEvent& event) {
const auto exploded_start_time =
calendar_utils::GetExplodedUTC(event.start_time().date_time());
const auto exploded_end_time =
calendar_utils::GetExplodedUTC(event.end_time().date_time());
if (event.start_time().date_time() < event.end_time().date_time() &&
exploded_start_time.hour == exploded_end_time.hour &&
exploded_start_time.minute == exploded_end_time.minute &&
exploded_start_time.second == exploded_end_time.second &&
exploded_start_time.millisecond == exploded_end_time.millisecond) {
return true;
}
return false;
}
bool ShouldShowEvent(const CalendarEvent& event) {
const auto now = base::Time::Now();
const auto& start_time = event.start_time().date_time();
const auto& end_time = event.end_time().date_time();
// Skip finished events.
if (end_time < now)
return false;
// Skip ongoing events 1.5 hours after the start time.
if (start_time <= now && now - start_time >= base::Hours(1.5))
return false;
// Skip all-day events.
if (IsAllDayEvent(event))
return false;
return true;
}
bool EventComparator(const CalendarEvent& a, const CalendarEvent& b) {
const auto& a_start_time = a.start_time().date_time();
const auto& a_end_time = a.end_time().date_time();
const auto& b_start_time = b.start_time().date_time();
const auto& b_end_time = b.end_time().date_time();
// If `a` and `b` have the same start and end time, then they are ordered
// alphabetically.
if (a_start_time == b_start_time && a_end_time == b_end_time)
return a.summary() < b.summary();
// If `a` and `b` have the same start time, then they are ordered based on
// duration (longer events ordered first).
if (a_start_time == b_start_time)
return a_end_time - a_start_time > b_end_time - b_start_time;
// Default ordering by start time.
return a_start_time < b_start_time;
}
} // namespace
// TODO(crbug.com/1353495): file-level todo list:
// - update existing `CalendarModel` and `CalendarEventFetch` to support 1-day
// fetches or consider implementing own simplified model (keep calendar-view
// team in the loop);
// - add "Loading" UI state;
// - refetch events at 00:00 and decide how to pull new events for current day;
// - correctly display multi-day events (limit to 00:00 and/or 23:59);
// - limit events list height and switch to `views::ScrollView`;
// - remove events from the list on their end times;
// - move fonts/colors/sizes to a config.
GlanceablesUpNextView::GlanceablesUpNextView() {
SetLayoutManager(std::make_unique<views::FlexLayout>());
calendar_model_ = Shell::Get()->system_tray_model()->calendar_model();
calendar_model_->AddObserver(this);
}
GlanceablesUpNextView::~GlanceablesUpNextView() {
calendar_model_->RemoveObserver(this);
}
void GlanceablesUpNextView::OnEventsFetched(
const CalendarModel::FetchingStatus status,
const base::Time start_time,
const google_apis::calendar::EventList* fetched_events) {
calendar_model_->RemoveObserver(this);
const SingleDayEventList up_next_events = GetUpNextEvents();
if (!up_next_events.empty())
CreateEventsListView(up_next_events);
else
AddNoEventsLabel();
}
SingleDayEventList GlanceablesUpNextView::GetUpNextEvents() {
const base::Time now = base::Time::Now();
const base::Time midnight =
(now + calendar_utils::GetTimeDifference(now)).UTCMidnight();
const SingleDayEventList& all_todays_events =
calendar_model_->FindEvents(midnight);
SingleDayEventList up_next_events;
for (const auto& event : all_todays_events) {
if (ShouldShowEvent(event))
up_next_events.push_back(event);
}
up_next_events.sort(&EventComparator);
return up_next_events;
}
void GlanceablesUpNextView::CreateEventsListView(
const SingleDayEventList& events) {
auto* events_list_view = AddChildView(std::make_unique<views::View>());
events_list_view->SetPreferredSize(gfx::Size(300, 150));
auto* events_list_layout =
events_list_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
events_list_layout->set_between_child_spacing(12);
for (const auto& event : events) {
event_item_views_.push_back(events_list_view->AddChildView(
std::make_unique<GlanceablesUpNextEventItemView>(event)));
}
}
void GlanceablesUpNextView::AddNoEventsLabel() {
no_events_label_ = AddChildView(std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_GLANCEABLES_UP_NEXT_NO_EVENTS)));
no_events_label_->SetAutoColorReadabilityEnabled(false);
no_events_label_->SetEnabledColor(gfx::kGoogleGrey200);
no_events_label_->SetFontList(gfx::FontList({"Google Sans"},
gfx::Font::FontStyle::NORMAL, 14,
gfx::Font::Weight::MEDIUM));
no_events_label_->SetHorizontalAlignment(
gfx::HorizontalAlignment::ALIGN_LEFT);
}
BEGIN_METADATA(GlanceablesUpNextView, views::View)
END_METADATA
} // namespace ash