#include "ash/system/time/time_view.h"
#include <memory>
#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/model/clock_model.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/calendar_utils.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_utils.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/i18n/rtl.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "third_party/icu/source/i18n/unicode/datefmt.h"
#include "third_party/icu/source/i18n/unicode/dtptngen.h"
#include "third_party/icu/source/i18n/unicode/smpdtfmt.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
const int kTimerSlopSeconds = 1;
const int kVerticalClockLeftPadding = 9;
const int kVerticalDateClockHorizontalPadding = 8;
const int kVerticalDateVerticalPadding = 2;
const int kDateTextSizeDiff = 4;
const int kVerticalClockMinutesTopOffset = -2;
std::u16string FormatDate(const base::Time& time) {
return base::TimeFormatWithPattern(time, "LLLd");
}
base::Time GetTimeToShow() {
if (!switches::IsStabilizeTimeDependentViewForTestsEnabled())
return base::Time::Now();
static base::Time fixed_time;
if (fixed_time.is_null())
CHECK(base::Time::FromString(kFakeNowTimeStringInPixelTest, &fixed_time));
return fixed_time;
}
}
VerticalDateView::VerticalDateView()
: icon_(AddChildView(std::make_unique<views::ImageView>())),
text_label_(AddChildView(std::make_unique<views::Label>())) {
SetLayoutManager(std::make_unique<views::FillLayout>());
text_label_->SetSubpixelRenderingEnabled(false);
text_label_->SetAutoColorReadabilityEnabled(false);
text_label_->SetFontList(
gfx::FontList().Derive(kTrayTextFontSizeIncrease - kDateTextSizeDiff,
gfx::Font::NORMAL, gfx::Font::Weight::BOLD));
text_label_->SetElideBehavior(gfx::NO_ELIDE);
UpdateText();
text_label_->SetBorder(views::CreateEmptyBorder(
gfx::Insets::TLBR(kVerticalDateVerticalPadding, 0, 0, 0)));
}
VerticalDateView::~VerticalDateView() = default;
void VerticalDateView::OnThemeChanged() {
views::View::OnThemeChanged();
text_label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary));
icon_->SetImage(gfx::CreateVectorIcon(
kCalendarBackgroundIcon,
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary)));
}
void VerticalDateView::UpdateText() {
const base::Time time_to_show = GetTimeToShow();
const std::u16string new_text = calendar_utils::GetDayIntOfMonth(
time_to_show + calendar_utils::GetTimeDifference(time_to_show));
if (text_label_->GetText() == new_text)
return;
text_label_->SetText(new_text);
text_label_->SetTooltipText(base::TimeFormatFriendlyDate(time_to_show));
}
TimeView::TimeView(ClockLayout clock_layout, ClockModel* model, Type type)
: ActionableView(TrayPopupInkDropStyle::INSET_BOUNDS),
model_(model),
type_(type) {
SetTimer(GetTimeToShow());
SetFocusBehavior(FocusBehavior::NEVER);
model_->AddObserver(this);
switch (type_) {
case kTime:
SetupSubviews(clock_layout);
break;
case kDate:
SetupDateviews(clock_layout);
break;
}
UpdateTextInternal(GetTimeToShow());
}
TimeView::~TimeView() {
model_->RemoveObserver(this);
timer_.Stop();
}
void TimeView::UpdateClockLayout(ClockLayout clock_layout) {
switch (type_) {
case kDate: {
if (clock_layout == ClockLayout::HORIZONTAL_CLOCK
? vertical_date_view_
: horizontal_date_view_) {
return;
}
if (clock_layout == ClockLayout::HORIZONTAL_CLOCK) {
vertical_date_view_ = RemoveChildViewT(children()[0]);
AddChildView(std::move(horizontal_date_view_));
} else {
horizontal_date_view_ = RemoveChildViewT(children()[0]);
AddChildView(std::move(vertical_date_view_));
}
break;
}
case kTime: {
if (clock_layout == ClockLayout::HORIZONTAL_CLOCK ? vertical_view_
: horizontal_view_) {
return;
}
if (clock_layout == ClockLayout::HORIZONTAL_CLOCK) {
vertical_view_ = RemoveChildViewT(children()[0]);
AddChildView(std::move(horizontal_view_));
} else {
horizontal_view_ = RemoveChildViewT(children()[0]);
AddChildView(std::move(vertical_view_));
}
break;
}
}
Layout();
}
void TimeView::SetTextColorId(ui::ColorId color_id,
bool auto_color_readability_enabled) {
auto set_color_id = [&](views::Label* label) {
label->SetEnabledColorId(color_id);
label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled);
};
switch (type_) {
case kTime:
set_color_id(horizontal_label_);
set_color_id(vertical_label_hours_);
set_color_id(vertical_label_minutes_);
return;
case kDate:
set_color_id(horizontal_label_date_);
}
}
void TimeView::SetTextColor(SkColor color,
bool auto_color_readability_enabled) {
auto set_color = [&](views::Label* label) {
label->SetEnabledColor(color);
label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled);
};
switch (type_) {
case kTime:
set_color(horizontal_label_);
set_color(vertical_label_hours_);
set_color(vertical_label_minutes_);
return;
case kDate:
set_color(horizontal_label_date_);
}
}
void TimeView::SetTextFont(const gfx::FontList& font_list) {
switch (type_) {
case kTime:
horizontal_label_->SetFontList(font_list);
vertical_label_hours_->SetFontList(font_list);
vertical_label_minutes_->SetFontList(font_list);
return;
case kDate:
horizontal_label_date_->SetFontList(font_list);
}
}
void TimeView::SetTextShadowValues(const gfx::ShadowValues& shadows) {
switch (type_) {
case kTime:
horizontal_label_->SetShadows(shadows);
vertical_label_hours_->SetShadows(shadows);
vertical_label_minutes_->SetShadows(shadows);
return;
case kDate:
horizontal_label_date_->SetShadows(shadows);
}
}
void TimeView::OnDateFormatChanged() {
UpdateTimeFormat();
}
void TimeView::OnSystemClockTimeUpdated() {
UpdateTimeFormat();
}
void TimeView::OnSystemClockCanSetTimeChanged(bool can_set_time) {}
void TimeView::Refresh() {
UpdateText();
}
base::HourClockType TimeView::GetHourTypeForTesting() const {
return model_->hour_clock_type();
}
bool TimeView::PerformAction(const ui::Event& event) {
return false;
}
void TimeView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
ActionableView::GetAccessibleNodeData(node_data);
node_data->role = ax::mojom::Role::kTime;
}
void TimeView::ChildPreferredSizeChanged(views::View* child) {
PreferredSizeChanged();
}
bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
return false;
}
void TimeView::OnGestureEvent(ui::GestureEvent* event) {
}
void TimeView::UpdateText() {
const base::Time now = GetTimeToShow();
UpdateTextInternal(now);
SchedulePaint();
SetTimer(now);
}
void TimeView::UpdateTimeFormat() {
UpdateText();
}
void TimeView::UpdateTextInternal(const base::Time& now) {
if (now.is_null()) {
LOG(ERROR) << "Received null value from base::Time |now| in argument";
return;
}
const std::u16string friendly_format_date = base::TimeFormatFriendlyDate(now);
SetAccessibleName(base::TimeFormatTimeOfDayWithHourClockType(
now, model_->hour_clock_type(), base::kKeepAmPm) +
u", " + friendly_format_date);
switch (type_) {
case kTime: {
const std::u16string current_time =
base::TimeFormatTimeOfDayWithHourClockType(
now, model_->hour_clock_type(), base::kDropAmPm);
const bool label_length_changed =
horizontal_label_->GetText().length() != current_time.length();
horizontal_label_->SetText(current_time);
horizontal_label_->SetTooltipText(friendly_format_date);
std::u16string current_hours =
(model_->hour_clock_type() == base::k24HourClock)
? calendar_utils::GetTwentyFourHourClockHours(now)
: calendar_utils::GetTwelveHourClockHours(now);
const std::u16string current_minutes = calendar_utils::GetMinutes(now);
vertical_label_hours_->SetText(current_hours);
vertical_label_minutes_->SetText(current_minutes);
Layout();
if (label_length_changed)
PreferredSizeChanged();
return;
}
case kDate: {
const std::u16string current_date = FormatDate(now);
horizontal_label_date_->SetText(current_date);
horizontal_label_date_->SetTooltipText(friendly_format_date);
date_view_->UpdateText();
}
}
}
void TimeView::SetupDateviews(ClockLayout clock_layout) {
DCHECK_EQ(type_, kDate);
SetLayoutManager(std::make_unique<views::FillLayout>());
horizontal_date_view_ = std::make_unique<View>();
horizontal_date_view_->SetLayoutManager(
std::make_unique<views::FillLayout>());
horizontal_date_view_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
kUnifiedTrayTextTopPadding, kUnifiedTrayTimeLeftPadding, 0, 0)));
horizontal_label_date_ =
horizontal_date_view_->AddChildView(std::make_unique<views::Label>());
SetupLabel(horizontal_label_date_);
vertical_date_view_ = std::make_unique<View>();
vertical_date_view_->SetLayoutManager(std::make_unique<views::FillLayout>());
date_view_ =
vertical_date_view_->AddChildView(std::make_unique<VerticalDateView>());
date_view_->SetBorder(
views::CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 0, 0)));
AddChildView(clock_layout == ClockLayout::HORIZONTAL_CLOCK
? std::move(horizontal_date_view_)
: std::move(vertical_date_view_));
}
void TimeView::SetupSubviews(ClockLayout clock_layout) {
DCHECK_EQ(type_, kTime);
horizontal_view_ = std::make_unique<View>();
horizontal_view_->SetLayoutManager(std::make_unique<views::FillLayout>());
horizontal_view_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
kUnifiedTrayTextTopPadding, kUnifiedTrayTimeLeftPadding, 0, 0)));
horizontal_label_ =
horizontal_view_->AddChildView(std::make_unique<views::Label>());
SetupLabel(horizontal_label_);
vertical_view_ = std::make_unique<View>();
vertical_view_->SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kVertical)
.SetMainAxisAlignment(views::LayoutAlignment::kCenter)
.SetCrossAxisAlignment(views::LayoutAlignment::kEnd)
.SetInteriorMargin(gfx::Insets::TLBR(0, kVerticalClockLeftPadding,
kVerticalClockMinutesTopOffset, 0));
vertical_label_hours_ =
vertical_view_->AddChildView(std::make_unique<views::Label>());
SetupLabel(vertical_label_hours_);
vertical_label_hours_->SetBorder(views::CreateEmptyBorder(
gfx::Insets::VH(0, kVerticalDateClockHorizontalPadding)));
vertical_label_minutes_ =
vertical_view_->AddChildView(std::make_unique<views::Label>());
SetupLabel(vertical_label_minutes_);
vertical_label_minutes_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
kVerticalClockMinutesTopOffset, kVerticalDateClockHorizontalPadding, 0,
kVerticalDateClockHorizontalPadding)));
SetLayoutManager(std::make_unique<views::FillLayout>());
AddChildView(clock_layout == ClockLayout::HORIZONTAL_CLOCK
? std::move(horizontal_view_)
: std::move(vertical_view_));
}
void TimeView::SetupLabel(views::Label* label) {
SetupLabelForTray(label);
label->SetElideBehavior(gfx::NO_ELIDE);
}
void TimeView::SetTimer(const base::Time& now) {
base::Time::Exploded exploded;
now.LocalExplode(&exploded);
int seconds_left = 60 - exploded.second;
if (seconds_left == 0)
seconds_left = 60;
seconds_left += kTimerSlopSeconds;
timer_.Stop();
timer_.Start(FROM_HERE, base::Seconds(seconds_left), this,
&TimeView::UpdateText);
}
BEGIN_METADATA(TimeView, ActionableView)
END_METADATA
}