blob: 9b5f59a5659610b1cea7074693d7850918e797e1 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/message_center/ash_notification_view.h"
#include <memory>
#include <utility>
#include "ash/public/cpp/rounded_image_view.h"
#include "ash/public/cpp/style/color_provider.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/icon_button.h"
#include "ash/style/pill_button.h"
#include "ash/style/style_util.h"
#include "ash/system/message_center/ash_notification_expand_button.h"
#include "ash/system/message_center/ash_notification_input_container.h"
#include "ash/system/message_center/message_center_constants.h"
#include "ash/system/message_center/message_center_style.h"
#include "ash/system/message_center/message_center_utils.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/wm/work_area_insets.h"
#include "base/bind.h"
#include "base/check.h"
#include "base/time/time.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_elider.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/vector_icons.h"
#include "ui/message_center/views/notification_background_painter.h"
#include "ui/message_center/views/notification_control_buttons_view.h"
#include "ui/message_center/views/notification_header_view.h"
#include "ui/message_center/views/notification_view_base.h"
#include "ui/message_center/views/proportional_image_view.h"
#include "ui/message_center/views/relative_time_formatter.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/focus_ring.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/box_layout_view.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/metadata/view_factory_internal.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
namespace {
constexpr gfx::Insets kNotificationViewPadding(0, 16, 18, 6);
constexpr gfx::Insets kMainRightViewPadding(0, 0, 0, 10);
constexpr int kMainRightViewVerticalSpacing = 4;
// This padding is applied to all the children of `main_right_view_` except the
// action buttons.
constexpr gfx::Insets kMainRightViewChildPadding(0, 14, 0, 0);
constexpr gfx::Insets kImageContainerPadding(12, 0, 0, 0);
constexpr gfx::Insets kActionButtonsRowPadding(16, 22, 0, 4);
constexpr int kActionsRowHorizontalSpacing = 8;
constexpr int kContentRowHorizontalSpacing = 16;
constexpr int kLeftContentVerticalSpacing = 4;
constexpr int kTitleRowSpacing = 6;
// Bullet character. The divider symbol between the title and the timestamp.
constexpr char16_t kTitleRowDivider[] = u"\u2022";
constexpr char kGoogleSansFont[] = "Google Sans";
constexpr int kAppIconViewSize = 24;
constexpr int kAppIconImageSize = 16;
constexpr int kTitleCharacterLimit =
message_center::kNotificationWidth * message_center::kMaxTitleLines /
message_center::kMinPixelsPerTitleCharacter;
constexpr int kTitleLabelSize = 14;
constexpr int kTimestampInCollapsedViewSize = 12;
constexpr int kMessageLabelSize = 13;
// The size for `icon_view_`, which is the icon within right content (between
// title/message view and expand button).
constexpr int kIconViewSize = 48;
// Lightness value that is used to calculate themed color used for app icon.
constexpr double kAppIconLightnessInDarkMode = 0.85;
constexpr double kAppIconLightnessInLightMode = 0.4;
// Helpers ---------------------------------------------------------------------
// Configure the style for labels in notification view. `is_color_primary`
// indicates if the color of the text is primary or secondary text color.
void ConfigureLabelStyle(
views::Label* label,
int size,
bool is_color_primary,
gfx::Font::Weight font_weight = gfx::Font::Weight::NORMAL) {
label->SetAutoColorReadabilityEnabled(false);
label->SetFontList(
gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, size, font_weight));
auto layer_type =
is_color_primary
? ash::AshColorProvider::ContentLayerType::kTextColorPrimary
: ash::AshColorProvider::ContentLayerType::kTextColorSecondary;
label->SetEnabledColor(
ash::AshColorProvider::Get()->GetContentLayerColor(layer_type));
}
// Create a view that will contain the `content_row`,
// `message_view_in_expanded_state_`, inline settings and the large image.
views::Builder<views::View> CreateMainRightViewBuilder() {
auto layout_manager = std::make_unique<views::FlexLayout>();
layout_manager
->SetDefault(views::kMarginsKey,
gfx::Insets(0, 0, kMainRightViewVerticalSpacing, 0))
.SetOrientation(views::LayoutOrientation::kVertical)
.SetInteriorMargin(kMainRightViewPadding);
return views::Builder<views::View>()
.SetID(message_center::NotificationViewBase::ViewId::kMainRightView)
.SetLayoutManager(std::move(layout_manager))
.SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded));
}
// Create a view containing the title and message for the notification in a
// single line. This is used when a grouped child notification is in a
// collapsed parent notification.
views::Builder<views::BoxLayoutView> CreateCollapsedSummaryBuilder(
const message_center::Notification& notification) {
return views::Builder<views::BoxLayoutView>()
.SetID(
message_center::NotificationViewBase::ViewId::kCollapsedSummaryView)
.SetInsideBorderInsets(ash::kGroupedCollapsedSummaryInsets)
.SetBetweenChildSpacing(ash::kGroupedCollapsedSummaryLabelSpacing)
.SetOrientation(views::BoxLayout::Orientation::kHorizontal)
.SetVisible(false)
.AddChild(views::Builder<views::Label>()
.SetText(notification.title())
.SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT))
.AddChild(views::Builder<views::Label>()
.SetText(notification.message())
.SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT)
.SetTextStyle(views::style::STYLE_SECONDARY));
}
// Perform a scale and translate animation by scale from (scale_value_x,
// scalue_value_y) and translate from (translate_value_x, translate_value_y) to
// its correct scale and position.
void ScaleAndTranslateView(views::View* view,
SkScalar scale_value_x,
SkScalar scale_value_y,
SkScalar translate_value_x,
SkScalar translate_value_y) {
gfx::Transform transform;
transform.Translate(translate_value_x, translate_value_y);
transform.Scale(scale_value_x, scale_value_y);
views::AnimationBuilder()
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.Once()
.SetDuration(base::TimeDelta())
.SetTransform(view, transform)
.Then()
.SetDuration(
base::Milliseconds(ash::kLargeImageScaleAndTranslateDurationMs))
.SetTransform(view, gfx::Transform(), gfx::Tween::ACCEL_0_100_DECEL_80);
}
} // namespace
namespace ash {
using CrossAxisAlignment = views::BoxLayout::CrossAxisAlignment;
using MainAxisAlignment = views::BoxLayout::MainAxisAlignment;
using Orientation = views::BoxLayout::Orientation;
BEGIN_METADATA(AshNotificationView, NotificationTitleRow, views::View)
END_METADATA
void AshNotificationView::GroupedNotificationsContainer::
ChildPreferredSizeChanged(views::View* view) {
PreferredSizeChanged();
parent_notification_view_->GroupedNotificationsPreferredSizeChanged();
}
void AshNotificationView::GroupedNotificationsContainer::
SetParentNotificationView(AshNotificationView* parent_notification_view) {
parent_notification_view_ = parent_notification_view;
}
AshNotificationView::NotificationTitleRow::NotificationTitleRow(
const std::u16string& title)
: title_view_(AddChildView(GenerateTitleView(title))),
title_row_divider_(AddChildView(std::make_unique<views::Label>(
kTitleRowDivider,
views::style::CONTEXT_DIALOG_BODY_TEXT))),
timestamp_in_collapsed_view_(
AddChildView(std::make_unique<views::Label>())) {
SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetDefault(views::kMarginsKey, gfx::Insets(0, 0, 0, kTitleRowSpacing));
title_view_->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kPreferred));
ConfigureLabelStyle(title_row_divider_, kTimestampInCollapsedViewSize,
/*is_color_primary=*/false);
message_center_utils::InitLayerForAnimations(title_row_divider_);
ConfigureLabelStyle(timestamp_in_collapsed_view_,
kTimestampInCollapsedViewSize,
/*is_color_primary=*/false);
message_center_utils::InitLayerForAnimations(timestamp_in_collapsed_view_);
ConfigureLabelStyle(title_view_, kTitleLabelSize,
/*is_color_primary=*/true, gfx::Font::Weight::MEDIUM);
}
AshNotificationView::NotificationTitleRow::~NotificationTitleRow() {
timestamp_update_timer_.Stop();
}
void AshNotificationView::NotificationTitleRow::UpdateTitle(
const std::u16string& title) {
title_view_->SetText(title);
}
void AshNotificationView::NotificationTitleRow::UpdateTimestamp(
base::Time timestamp) {
std::u16string relative_time;
base::TimeDelta next_update;
message_center::GetRelativeTimeStringAndNextUpdateTime(
timestamp - base::Time::Now(), &relative_time, &next_update);
timestamp_ = timestamp;
timestamp_in_collapsed_view_->SetText(relative_time);
// Unretained is safe as the timer cancels the task on destruction.
timestamp_update_timer_.Start(
FROM_HERE, next_update,
base::BindOnce(&NotificationTitleRow::UpdateTimestamp,
base::Unretained(this), timestamp));
}
void AshNotificationView::NotificationTitleRow::UpdateVisibility(
bool in_collapsed_mode) {
timestamp_in_collapsed_view_->SetVisible(in_collapsed_mode);
title_row_divider_->SetVisible(in_collapsed_mode);
}
void AshNotificationView::NotificationTitleRow::
PerformExpandCollapseAnimation() {
if (title_row_divider_->GetVisible()) {
message_center_utils::FadeInView(
title_row_divider_, kTitleRowTimestampFadeInAnimationDelayMs,
kTitleRowTimestampFadeInAnimationDurationMs,
gfx::Tween::ACCEL_20_DECEL_100);
DCHECK(timestamp_in_collapsed_view_->GetVisible());
message_center_utils::FadeInView(
timestamp_in_collapsed_view_, kTitleRowTimestampFadeInAnimationDelayMs,
kTitleRowTimestampFadeInAnimationDurationMs,
gfx::Tween::ACCEL_20_DECEL_100);
}
}
// static
const char AshNotificationView::kViewClassName[] = "AshNotificationView";
AshNotificationView::AshNotificationView(
const message_center::Notification& notification,
bool shown_in_popup)
: NotificationViewBase(notification), shown_in_popup_(shown_in_popup) {
// TODO(crbug/1232197): fix views and layout to match spec.
// Instantiate view instances and define layout and view hierarchy.
SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kVertical)
.SetInteriorMargin(notification.group_child() ? gfx::Insets()
: kNotificationViewPadding);
auto content_row_layout = std::make_unique<views::FlexLayout>();
content_row_layout->SetInteriorMargin(kMainRightViewChildPadding);
auto content_row_builder =
CreateContentRowBuilder()
.SetLayoutManager(std::move(content_row_layout))
.AddChild(
views::Builder<views::BoxLayoutView>()
.SetID(kHeaderLeftContent)
.SetOrientation(Orientation::kVertical)
.SetBetweenChildSpacing(kLeftContentVerticalSpacing)
.SetProperty(views::kFlexBehaviorKey,
views::FlexSpecification(
views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kScaleToMaximum))
.AddChild(
CreateHeaderRowBuilder()
.SetIsInAshNotificationView(true)
.SetColor(
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::
kTextColorSecondary)))
.AddChild(
CreateLeftContentBuilder()
.CopyAddressTo(&left_content_)
.SetBetweenChildSpacing(kLeftContentVerticalSpacing)))
.AddChild(
views::Builder<views::BoxLayoutView>()
.SetMainAxisAlignment(MainAxisAlignment::kEnd)
.SetInsideBorderInsets(
gfx::Insets(0, kContentRowHorizontalSpacing, 0, 0))
.SetBetweenChildSpacing(kContentRowHorizontalSpacing)
.SetProperty(views::kFlexBehaviorKey,
views::FlexSpecification(
views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded))
.AddChild(CreateRightContentBuilder())
.AddChild(
views::Builder<views::FlexLayoutView>()
.CopyAddressTo(&expand_button_container_)
.SetOrientation(views::LayoutOrientation::kHorizontal)
.AddChild(
views::Builder<AshNotificationExpandButton>()
.CopyAddressTo(&expand_button_)
.SetCallback(base::BindRepeating(
&AshNotificationView::ToggleExpand,
base::Unretained(this)))
.SetProperty(
views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kStart))));
// Main right view contains all the views besides control buttons and
// icon.
auto main_right_view_builder =
CreateMainRightViewBuilder()
.CopyAddressTo(&main_right_view_)
.AddChild(content_row_builder)
.AddChild(
views::Builder<views::Label>()
.CopyAddressTo(&message_view_in_expanded_state_)
.SetHorizontalAlignment(gfx::ALIGN_TO_HEAD)
.SetMultiLine(true)
.SetMaxLines(message_center::kMaxLinesForExpandedMessageView)
.SetAllowCharacterBreak(true)
.SetBorder(
views::CreateEmptyBorder(kMainRightViewChildPadding))
// TODO(crbug/682266): This is a workaround to that bug by
// explicitly setting the width. Ideally, we should fix the
// original bug, but it seems there's no obvious solution for
// the bug according to https://crbug.com/678337#c7. We will
// consider making changes to this code when the bug is fixed.
.SetMaximumWidth(GetExpandedMessageViewWidth()))
.AddChild(CreateInlineSettingsBuilder())
.AddChild(CreateImageContainerBuilder().SetBorder(
views::CreateEmptyBorder(kImageContainerPadding)));
ConfigureLabelStyle(message_view_in_expanded_state_, kMessageLabelSize,
false);
AddChildView(
views::Builder<views::BoxLayoutView>()
.CopyAddressTo(&control_buttons_container_)
.SetInsideBorderInsets(IsExpanded()
? kControlButtonsContainerExpandedPadding
: kControlButtonsContainerCollapsedPadding)
.SetMainAxisAlignment(MainAxisAlignment::kEnd)
.SetVisible(!notification.group_child())
.AddChild(CreateControlButtonsBuilder()
.CopyAddressTo(&control_buttons_view_)
.SetButtonIconColors(
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::
kIconColorPrimary)))
.Build());
AddChildView(
views::Builder<views::FlexLayoutView>()
.CopyAddressTo(&main_view_)
.SetOrientation(views::LayoutOrientation::kHorizontal)
.AddChild(views::Builder<views::BoxLayoutView>()
.SetID(kAppIconViewContainer)
.SetOrientation(Orientation::kVertical)
.SetMainAxisAlignment(MainAxisAlignment::kStart)
.AddChild(views::Builder<RoundedImageView>()
.CopyAddressTo(&app_icon_view_)
.SetCornerRadius(kAppIconViewSize / 2)))
.AddChild(main_right_view_builder)
.Build());
AddChildView(CreateCollapsedSummaryBuilder(notification)
.CopyAddressTo(&collapsed_summary_view_)
.Build());
if (!notification.group_child()) {
AddChildView(
views::Builder<views::ScrollView>()
.CopyAddressTo(&grouped_notifications_scroll_view_)
.SetBackgroundColor(absl::nullopt)
.SetDrawOverflowIndicator(false)
.ClipHeightTo(0, std::numeric_limits<int>::max())
.SetContents(
views::Builder<GroupedNotificationsContainer>()
.CopyAddressTo(&grouped_notifications_container_)
.SetParentNotificationView(this)
.SetOrientation(Orientation::kVertical)
.SetInsideBorderInsets(kGroupedNotificationContainerInsets)
.SetBetweenChildSpacing(
IsExpanded() ? kGroupedNotificationsExpandedSpacing
: kGroupedNotificationsCollapsedSpacing))
.Build());
}
AddChildView(CreateActionsRow(std::make_unique<views::FlexLayout>()));
// Custom layout and paddings for views in `AshNotificationView`.
action_buttons_row()
->SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetDefault(views::kMarginsKey,
gfx::Insets(0, 0, 0, kActionsRowHorizontalSpacing))
.SetOrientation(views::LayoutOrientation::kHorizontal)
.SetInteriorMargin(kActionButtonsRowPadding);
action_buttons_row()->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded));
inline_reply()->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded));
static_cast<views::FlexLayout*>(header_row()->GetLayoutManager())
->SetDefault(views::kMarginsKey, gfx::Insets())
.SetInteriorMargin(gfx::Insets());
header_row()->ConfigureLabelsStyle(
gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, kHeaderViewLabelSize,
gfx::Font::Weight::NORMAL),
gfx::Insets(), true);
if (shown_in_popup_ && !notification.group_child()) {
layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF{kMessagePopupCornerRadius});
} else if (!notification.group_child()) {
layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF{kMessageCenterNotificationCornerRadius});
}
layer()->SetIsFastRoundedCorner(true);
// Create layer in some views for animations.
message_center_utils::InitLayerForAnimations(header_row());
message_center_utils::InitLayerForAnimations(message_view_in_expanded_state_);
message_center_utils::InitLayerForAnimations(actions_row());
UpdateWithNotification(notification);
}
AshNotificationView::~AshNotificationView() = default;
void AshNotificationView::SetGroupedChildExpanded(bool expanded) {
collapsed_summary_view_->SetVisible(!expanded);
main_view_->SetVisible(expanded);
control_buttons_container_->SetVisible(expanded);
}
void AshNotificationView::AnimateGroupedChildExpandedCollapse(bool expanded) {
message_center_utils::InitLayerForAnimations(collapsed_summary_view_);
message_center_utils::InitLayerForAnimations(main_view_);
// Fade out `collapsed_summary_view_`, then fade in `main_view_` in expanded
// state and vice versa in collapsed state.
if (expanded) {
message_center_utils::FadeOutView(
collapsed_summary_view_,
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* collapsed_summary_view) {
if (parent) {
collapsed_summary_view->layer()->SetOpacity(1.0f);
collapsed_summary_view->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), collapsed_summary_view_),
0, kCollapsedSummaryViewAnimationDurationMs);
message_center_utils::FadeInView(main_view_,
kCollapsedSummaryViewAnimationDurationMs,
kChildMainViewFadeInAnimationDurationMs);
return;
}
message_center_utils::FadeOutView(
main_view_,
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* main_view) {
if (parent) {
main_view->layer()->SetOpacity(1.0f);
main_view->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), main_view_),
0, kChildMainViewFadeOutAnimationDurationMs);
message_center_utils::FadeInView(collapsed_summary_view_,
kChildMainViewFadeOutAnimationDurationMs,
kCollapsedSummaryViewAnimationDurationMs);
}
void AshNotificationView::ToggleExpand() {
SetManuallyExpandedOrCollapsed(true);
if (inline_reply()->GetVisible()) {
// If inline reply is visible, fade it out then set expanded.
message_center_utils::FadeOutView(
inline_reply(),
base::BindOnce(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* inline_reply) {
if (parent) {
inline_reply->layer()->SetOpacity(1.0f);
}
},
weak_factory_.GetWeakPtr(), inline_reply()),
/*delay_in_ms=*/0, kInlineReplyFadeOutAnimationDurationMs);
}
SetExpanded(!IsExpanded());
PerformExpandCollapseAnimation();
}
void AshNotificationView::GroupedNotificationsPreferredSizeChanged() {
PreferredSizeChanged();
}
base::TimeDelta AshNotificationView::GetBoundsAnimationDuration(
const message_center::Notification& notification) const {
// This is called after the parent gets notified of
// `ChildPreferredSizeChanged()`, so the current expanded state is the target
// state.
if (!notification.image().IsEmpty())
return base::Milliseconds(kLargeImageExpandAndCollapseAnimationDuration);
if (HasInlineReply(notification) || is_grouped_parent_view_) {
if (IsExpanded()) {
return base::Milliseconds(
kInlineReplyAndGroupedParentExpandAnimationDuration);
}
return base::Milliseconds(
kInlineReplyAndGroupedParentCollapseAnimationDuration);
}
if (inline_settings_row() && inline_settings_row()->GetVisible()) {
return base::Milliseconds(
kInlineSettingsExpandAndCollapseAnimationDuration);
}
if (IsExpanded())
return base::Milliseconds(kGeneralExpandAnimationDuration);
return base::Milliseconds(kGeneralCollapseAnimationDuration);
}
void AshNotificationView::AddGroupNotification(
const message_center::Notification& notification,
bool newest_first) {
auto notification_view =
std::make_unique<AshNotificationView>(notification,
/*shown_in_popup=*/false);
notification_view->SetVisible(
total_grouped_notifications_ <
message_center_style::kMaxGroupedNotificationsInCollapsedState ||
IsExpanded());
notification_view->SetGroupedChildExpanded(IsExpanded());
grouped_notifications_container_->AddChildViewAt(
std::move(notification_view),
newest_first ? 0 : grouped_notifications_container_->children().size());
total_grouped_notifications_++;
left_content_->SetVisible(false);
expand_button_->UpdateGroupedNotificationsCount(total_grouped_notifications_);
PreferredSizeChanged();
}
void AshNotificationView::PopulateGroupNotifications(
const std::vector<const message_center::Notification*>& notifications) {
DCHECK(total_grouped_notifications_ == 0);
for (auto* notification : notifications) {
auto notification_view =
std::make_unique<AshNotificationView>(*notification,
/*shown_in_popup=*/false);
notification_view->SetVisible(
total_grouped_notifications_ <
message_center_style::kMaxGroupedNotificationsInCollapsedState ||
IsExpanded());
notification_view->SetGroupedChildExpanded(IsExpanded());
grouped_notifications_container_->AddChildViewAt(
std::move(notification_view), 0);
}
total_grouped_notifications_ = notifications.size();
left_content_->SetVisible(total_grouped_notifications_ == 0);
expand_button_->UpdateGroupedNotificationsCount(total_grouped_notifications_);
}
void AshNotificationView::RemoveGroupNotification(
const std::string& notification_id) {
AshNotificationView* to_be_deleted = nullptr;
for (auto* child : grouped_notifications_container_->children()) {
AshNotificationView* group_notification =
static_cast<AshNotificationView*>(child);
if (group_notification->notification_id() == notification_id) {
to_be_deleted = group_notification;
break;
}
}
if (to_be_deleted)
delete to_be_deleted;
total_grouped_notifications_--;
left_content_->SetVisible(total_grouped_notifications_ == 0);
expand_button_->UpdateGroupedNotificationsCount(total_grouped_notifications_);
PreferredSizeChanged();
}
const char* AshNotificationView::GetClassName() const {
return kViewClassName;
}
void AshNotificationView::UpdateViewForExpandedState(bool expanded) {
static_cast<views::BoxLayout*>(control_buttons_container_->GetLayoutManager())
->set_inside_border_insets(
expanded ? kControlButtonsContainerExpandedPadding
: kControlButtonsContainerCollapsedPadding);
bool is_single_expanded_notification =
!is_grouped_child_view_ && !is_grouped_parent_view_ && expanded;
header_row()->SetVisible(is_grouped_parent_view_ ||
(is_single_expanded_notification));
if (title_row_) {
title_row_->UpdateVisibility(is_grouped_child_view_ ||
(IsExpandable() && !expanded));
}
if (message_view()) {
// `message_view()` is shown only in collapsed mode.
if (!expanded) {
ConfigureLabelStyle(message_view(), kMessageLabelSize, false);
message_center_utils::InitLayerForAnimations(message_view());
}
message_view()->SetVisible(!expanded);
message_view_in_expanded_state_->SetVisible(expanded &&
!is_grouped_parent_view_);
}
// Custom padding for app icon and expand button. These 2 views should always
// use the same padding value so that they are vertical aligned.
app_icon_view_->SetBorder(views::CreateEmptyBorder(
expanded ? kAppIconExpandButtonExpandedPadding
: kAppIconExpandButtonCollapsedPadding));
expand_button_container_->SetInteriorMargin(
expanded ? kAppIconExpandButtonExpandedPadding
: kAppIconExpandButtonCollapsedPadding);
expand_button_->SetExpanded(expanded);
if (is_grouped_parent_view_) {
if (shown_in_popup_) {
grouped_notifications_scroll_view_->ClipHeightTo(
0, CalculateMaxHeightForGroupedNotifications());
}
static_cast<views::BoxLayout*>(
grouped_notifications_container_->GetLayoutManager())
->set_between_child_spacing(
expanded ? kGroupedNotificationsExpandedSpacing
: kGroupedNotificationsCollapsedSpacing);
int notification_count = 0;
for (auto* child : grouped_notifications_container_->children()) {
auto* notification_view = static_cast<AshNotificationView*>(child);
notification_view->AnimateGroupedChildExpandedCollapse(expanded);
notification_view->SetGroupedChildExpanded(expanded);
notification_count++;
if (notification_count >
message_center_style::kMaxGroupedNotificationsInCollapsedState) {
notification_view->SetVisible(expanded);
}
}
}
NotificationViewBase::UpdateViewForExpandedState(expanded);
}
void AshNotificationView::UpdateWithNotification(
const message_center::Notification& notification) {
is_grouped_child_view_ = notification.group_child();
is_grouped_parent_view_ = notification.group_parent();
if (!is_grouped_child_view_)
grouped_notifications_container_->SetVisible(is_grouped_parent_view_);
header_row()->SetVisible(!is_grouped_child_view_);
UpdateMessageViewInExpandedState(notification);
NotificationViewBase::UpdateWithNotification(notification);
CreateOrUpdateSnoozeButton(notification);
UpdateIconAndButtonsColor();
}
void AshNotificationView::CreateOrUpdateHeaderView(
const message_center::Notification& notification) {
switch (notification.system_notification_warning_level()) {
case message_center::SystemNotificationWarningLevel::WARNING:
header_row()->SetSummaryText(
l10n_util::GetStringUTF16(IDS_ASH_NOTIFICATION_WARNING_LABEL));
break;
case message_center::SystemNotificationWarningLevel::CRITICAL_WARNING:
header_row()->SetSummaryText(l10n_util::GetStringUTF16(
IDS_ASH_NOTIFICATION_CRITICAL_WARNING_LABEL));
break;
case message_center::SystemNotificationWarningLevel::NORMAL:
header_row()->SetSummaryText(std::u16string());
break;
}
NotificationViewBase::CreateOrUpdateHeaderView(notification);
}
void AshNotificationView::CreateOrUpdateTitleView(
const message_center::Notification& notification) {
if (notification.title().empty()) {
if (title_row_) {
DCHECK(left_content()->Contains(title_row_));
left_content()->RemoveChildViewT(title_row_);
title_row_ = nullptr;
}
return;
}
const std::u16string& title = gfx::TruncateString(
notification.title(), kTitleCharacterLimit, gfx::WORD_BREAK);
if (!title_row_) {
title_row_ =
AddViewToLeftContent(std::make_unique<NotificationTitleRow>(title));
} else {
title_row_->UpdateTitle(title);
ReorderViewInLeftContent(title_row_);
}
title_row_->UpdateTimestamp(notification.timestamp());
}
void AshNotificationView::CreateOrUpdateSmallIconView(
const message_center::Notification& notification) {
if (is_grouped_child_view_ && !notification.icon().IsEmpty()) {
app_icon_view_->SetImage(notification.icon().AsImageSkia(),
gfx::Size(kAppIconViewSize, kAppIconViewSize));
return;
}
UpdateAppIconView();
}
void AshNotificationView::CreateOrUpdateInlineSettingsViews(
const message_center::Notification& notification) {
if (inline_settings_enabled()) {
// TODO(crbug/1265636): Fix this logic when grouped parent notification has
// inline settings.
DCHECK(is_grouped_parent_view_ ||
(message_center::SettingsButtonHandler::INLINE ==
notification.rich_notification_data().settings_button_handler));
return;
}
set_inline_settings_enabled(
notification.rich_notification_data().settings_button_handler ==
message_center::SettingsButtonHandler::INLINE);
if (!inline_settings_enabled()) {
return;
}
inline_settings_row()->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0));
auto turn_off_notifications_button = GenerateNotificationLabelButton(
base::BindRepeating(&AshNotificationView::DisableNotification,
base::Unretained(this)),
l10n_util::GetStringUTF16(
IDS_ASH_NOTIFICATION_INLINE_SETTINGS_TURN_OFF_BUTTON_TEXT));
turn_off_notifications_button_ = inline_settings_row()->AddChildView(
std::move(turn_off_notifications_button));
auto inline_settings_cancel_button = GenerateNotificationLabelButton(
base::BindRepeating(&AshNotificationView::ToggleInlineSettings,
base::Unretained(this)),
l10n_util::GetStringUTF16(
IDS_ASH_NOTIFICATION_INLINE_SETTINGS_CANCEL_BUTTON_TEXT));
inline_settings_cancel_button_ = inline_settings_row()->AddChildView(
std::move(inline_settings_cancel_button));
}
void AshNotificationView::UpdateControlButtonsVisibility() {
NotificationViewBase::UpdateControlButtonsVisibility();
// Always hide snooze button in control buttons since we show this snooze
// button in actions button view.
control_buttons_view()->ShowSnoozeButton(false);
// Hide settings button for grouped child notifications.
if (is_grouped_child_view_)
control_buttons_view()->ShowSettingsButton(false);
}
bool AshNotificationView::IsIconViewShown() const {
return NotificationViewBase::IsIconViewShown() && !is_grouped_child_view_;
}
void AshNotificationView::SetExpandButtonEnabled(bool enabled) {
expand_button_->SetVisible(enabled);
}
bool AshNotificationView::IsExpandable() const {
// Inline settings can not be expanded.
if (GetMode() == Mode::SETTING)
return false;
// Notification should always be expandable since we hide `header_row()` in
// collapsed state.
return true;
}
void AshNotificationView::UpdateCornerRadius(int top_radius,
int bottom_radius) {
// Call parent's SetCornerRadius to update radius used for highlight path.
NotificationViewBase::SetCornerRadius(top_radius, bottom_radius);
UpdateBackground(top_radius, bottom_radius);
}
void AshNotificationView::SetDrawBackgroundAsActive(bool active) {}
void AshNotificationView::OnThemeChanged() {
views::View::OnThemeChanged();
UpdateBackground(top_radius_, bottom_radius_);
header_row()->SetColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorSecondary));
views::FocusRing::Get(this)->SetColor(
AshColorProvider::Get()->GetControlsLayerColor(
AshColorProvider::ControlsLayerType::kFocusRingColor));
UpdateIconAndButtonsColor();
}
std::unique_ptr<message_center::NotificationInputContainer>
AshNotificationView::GenerateNotificationInputContainer() {
return std::make_unique<AshNotificationInputContainer>(this);
}
std::unique_ptr<views::LabelButton>
AshNotificationView::GenerateNotificationLabelButton(
views::Button::PressedCallback callback,
const std::u16string& label) {
std::unique_ptr<views::LabelButton> actions_button =
std::make_unique<PillButton>(std::move(callback), label,
PillButton::Type::kIconlessAccentFloating,
/*icon=*/nullptr);
// Override the inkdrop configuration to make sure it will show up when hover
// or focus on the button.
StyleUtil::SetUpInkDropForButton(actions_button.get(), gfx::Insets(),
/*highlight_on_hover=*/true,
/*highlight_on_focus=*/true);
return actions_button;
}
gfx::Size AshNotificationView::GetIconViewSize() const {
return gfx::Size(kIconViewSize, kIconViewSize);
}
int AshNotificationView::GetLargeImageViewMaxWidth() const {
return message_center::kNotificationWidth - kNotificationViewPadding.width() -
kAppIconViewSize - kMainRightViewPadding.width() -
kMainRightViewChildPadding.width();
}
void AshNotificationView::ToggleInlineSettings(const ui::Event& event) {
if (!inline_settings_enabled())
return;
bool should_show_inline_settings = !inline_settings_row()->GetVisible();
PerformToggleInlineSettingsAnimation(should_show_inline_settings);
NotificationViewBase::ToggleInlineSettings(event);
if (is_grouped_parent_view_) {
grouped_notifications_scroll_view_->SetVisible(
!should_show_inline_settings);
} else {
// In settings UI, we only show the app icon and header row along with the
// inline settings UI.
header_row()->SetVisible(true);
left_content()->SetVisible(!should_show_inline_settings);
right_content()->SetVisible(!should_show_inline_settings);
}
expand_button_->SetVisible(!should_show_inline_settings);
PreferredSizeChanged();
}
void AshNotificationView::ActionButtonPressed(size_t index,
const ui::Event& event) {
NotificationViewBase::ActionButtonPressed(index, event);
// If inline reply is visible, fade out actions button and then fade in inline
// reply.
if (inline_reply()->GetVisible()) {
message_center_utils::InitLayerForAnimations(action_buttons_row());
message_center_utils::FadeOutView(
action_buttons_row(),
base::BindOnce(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* action_buttons_row) {
if (parent) {
action_buttons_row->layer()->SetOpacity(1.0f);
action_buttons_row->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), action_buttons_row()),
/*delay_in_ms=*/0, kActionButtonsFadeOutAnimationDurationMs);
// Delay for the action buttons to fade out, then fade in inline reply.
message_center_utils::InitLayerForAnimations(inline_reply());
message_center_utils::FadeInView(inline_reply(),
kActionButtonsFadeOutAnimationDurationMs,
kInlineReplyFadeInAnimationDurationMs);
}
}
void AshNotificationView::CreateOrUpdateSnoozeButton(
const message_center::Notification& notification) {
if (!notification.should_show_snooze_button()) {
if (action_buttons_row()->Contains(snooze_button_)) {
action_buttons_row()->RemoveChildViewT(snooze_button_);
snooze_button_ = nullptr;
DCHECK(action_buttons_row()->Contains(snooze_button_spacer_));
action_buttons_row()->RemoveChildViewT(snooze_button_spacer_);
snooze_button_spacer_ = nullptr;
}
return;
}
if (snooze_button_) {
DCHECK(snooze_button_spacer_);
// Spacer and snooze button should be at the end of action buttons row.
action_buttons_row()->ReorderChildView(snooze_button_spacer_, -1);
action_buttons_row()->ReorderChildView(snooze_button_, -1);
return;
}
action_buttons_row()->AddChildView(
views::Builder<views::BoxLayoutView>()
.CopyAddressTo(&snooze_button_spacer_)
.SetMainAxisAlignment(MainAxisAlignment::kEnd)
.SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded))
.Build());
auto snooze_button = std::make_unique<IconButton>(
base::BindRepeating(&AshNotificationView::OnSnoozeButtonPressed,
base::Unretained(this)),
IconButton::Type::kSmallFloating, &kNotificationSnoozeButtonIcon,
IDS_MESSAGE_CENTER_NOTIFICATION_SNOOZE_BUTTON_TOOLTIP);
snooze_button_ = action_buttons_row()->AddChildView(std::move(snooze_button));
}
void AshNotificationView::UpdateMessageViewInExpandedState(
const message_center::Notification& notification) {
if (notification.message().empty()) {
message_view_in_expanded_state_->SetVisible(false);
return;
}
message_view_in_expanded_state_->SetText(gfx::TruncateString(
notification.message(), message_center::kMessageCharacterLimit,
gfx::WORD_BREAK));
message_view_in_expanded_state_->SetVisible(true);
}
void AshNotificationView::UpdateBackground(int top_radius, int bottom_radius) {
SkColor background_color;
if (shown_in_popup_) {
background_color = AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kTransparent80);
} else {
background_color = AshColorProvider::Get()->GetControlsLayerColor(
AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive);
}
if (background_color == background_color_ && top_radius_ == top_radius &&
bottom_radius_ == bottom_radius) {
return;
}
if (!is_grouped_child_view_)
background_color_ = background_color;
top_radius_ = top_radius;
bottom_radius_ = bottom_radius;
SetBackground(views::CreateBackgroundFromPainter(
std::make_unique<message_center::NotificationBackgroundPainter>(
top_radius_, bottom_radius_, background_color_)));
}
int AshNotificationView::GetExpandedMessageViewWidth() {
int notification_width = shown_in_popup_ ? message_center::kNotificationWidth
: kNotificationInMessageCenterWidth;
return notification_width - kNotificationViewPadding.width() -
kAppIconViewSize - kMainRightViewPadding.width() -
kMainRightViewChildPadding.width();
}
void AshNotificationView::DisableNotification() {
message_center::MessageCenter::Get()->DisableNotification(notification_id());
}
void AshNotificationView::UpdateAppIconView() {
auto* notification =
message_center::MessageCenter::Get()->FindVisibleNotificationById(
notification_id());
// Grouped child notification use notification's icon for the app icon view,
// so we don't need further update here.
if (!notification ||
(is_grouped_child_view_ && !notification->icon().IsEmpty()))
return;
SkColor icon_color = AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kTransparent80);
SkColor icon_background_color = CalculateIconAndButtonsColor();
// TODO(crbug.com/768748): figure out if this has a performance impact and
// cache images if so.
gfx::Image masked_small_icon = notification->GenerateMaskedSmallIcon(
kAppIconImageSize, icon_color, icon_background_color, icon_color);
gfx::ImageSkia app_icon =
masked_small_icon.IsEmpty()
? gfx::CreateVectorIcon(message_center::kProductIcon,
kAppIconImageSize, icon_color)
: masked_small_icon.AsImageSkia();
app_icon_view_->SetImage(
gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
kAppIconViewSize / 2, icon_background_color, app_icon));
}
SkColor AshNotificationView::CalculateIconAndButtonsColor() {
auto* notification =
message_center::MessageCenter::Get()->FindVisibleNotificationById(
notification_id());
SkColor default_color = AshColorProvider::Get()->GetControlsLayerColor(
AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
if (!notification)
return default_color;
SkColor accent_color = notification->accent_color().value_or(default_color);
// To ensure the icon and buttons look distinct enough in the notification
// background, we change the lightness of the accent color to generate the
// desired color.
color_utils::HSL hsl;
color_utils::SkColorToHSL(accent_color, &hsl);
hsl.l = AshColorProvider::Get()->IsDarkModeEnabled()
? kAppIconLightnessInDarkMode
: kAppIconLightnessInLightMode;
return color_utils::HSLToSkColor(hsl, SkColorGetA(accent_color));
}
void AshNotificationView::UpdateIconAndButtonsColor() {
UpdateAppIconView();
SkColor button_color = CalculateIconAndButtonsColor();
for (auto* action_button : action_buttons()) {
static_cast<PillButton*>(action_button)->SetButtonTextColor(button_color);
}
if (snooze_button_)
snooze_button_->SetIconColor(button_color);
}
void AshNotificationView::PerformExpandCollapseAnimation() {
if (title_row_)
title_row_->PerformExpandCollapseAnimation();
// Fade in `header row()` if this is not a grouped parent view.
if (header_row()->GetVisible() && !is_grouped_parent_view_) {
message_center_utils::FadeInView(header_row(),
kHeaderRowFadeInAnimationDelayMs,
kHeaderRowFadeInAnimationDurationMs);
}
// Fade in `message_view()`. We only do fade in for both message view in
// expanded and collapsed mode if there's a difference between them (a.k.a
// when `message_view()` is truncated).
if (message_view()->GetVisible() &&
message_view()->IsDisplayTextTruncated()) {
message_center_utils::FadeInView(message_view(),
kMessageViewFadeInAnimationDelayMs,
kMessageViewFadeInAnimationDurationMs);
}
// Fade in `message_view_in_expanded_state_`.
if (message_view_in_expanded_state_->GetVisible() &&
message_view()->IsDisplayTextTruncated()) {
message_center_utils::FadeInView(
message_view_in_expanded_state_,
kMessageViewInExpandedStateFadeInAnimationDelayMs,
kMessageViewInExpandedStateFadeInAnimationDurationMs);
}
if (!image_container_view()->children().empty()) {
PerformLargeImageAnimation();
}
if (actions_row()->GetVisible()) {
message_center_utils::FadeInView(actions_row(),
kActionsRowFadeInAnimationDelayMs,
kActionsRowFadeInAnimationDurationMs);
}
if (total_grouped_notifications_) {
// Ensure layout is up-to-date before animating expand button. This is used
// for its bounds animation.
if (needs_layout())
Layout();
DCHECK(!needs_layout());
expand_button_->PerformExpandCollapseAnimation();
}
}
void AshNotificationView::PerformLargeImageAnimation() {
message_center_utils::InitLayerForAnimations(image_container_view());
message_center_utils::InitLayerForAnimations(icon_view());
auto icon_view_bounds = icon_view()->GetBoundsInScreen();
auto large_image_bounds = image_container_view()->GetBoundsInScreen();
if (IsExpanded()) {
// In expanded state, do a scale and translate from `icon_view()` to
// `image_container_view()`.
message_center_utils::InitLayerForAnimations(image_container_view());
ScaleAndTranslateView(image_container_view(),
static_cast<double>(icon_view()->width()) /
image_container_view()->width(),
static_cast<double>(icon_view()->height()) /
image_container_view()->height(),
icon_view_bounds.x() - large_image_bounds.x(),
icon_view_bounds.y() - large_image_bounds.y());
// If we a different image for `icon_view()` and `image_container_view()`
// (a.k.a hide_icon_on_expanded() is false), fade in
// `image_container_view()`.
if (!hide_icon_on_expanded()) {
message_center_utils::FadeInView(image_container_view(),
kLargeImageFadeInAnimationDelayMs,
kLargeImageFadeInAnimationDurationMs);
}
return;
}
if (hide_icon_on_expanded()) {
// In collapsed state, if we use a same image for `icon_view()` and
// `image_container_view()`, perform a scale and translate from
// `image_container_view()` to `icon_view()`.
ScaleAndTranslateView(
icon_view(),
static_cast<double>(image_container_view()->width()) /
icon_view()->width(),
static_cast<double>(image_container_view()->height()) /
icon_view()->height(),
large_image_bounds.x() - icon_view_bounds.x(),
large_image_bounds.y() - icon_view_bounds.y());
return;
}
// In collapsed state, if we use a different image for `icon_view()` and
// `image_container_view()`, fade out and scale down `image_container_view()`.
message_center_utils::FadeOutView(
image_container_view(),
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* image_container_view) {
if (parent) {
image_container_view->layer()->SetOpacity(1.0f);
//
image_container_view->layer()->SetTransform(gfx::Transform());
image_container_view->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), image_container_view()),
kLargeImageFadeOutAnimationDelayMs, kLargeImageFadeOutAnimationDurationMs,
gfx::Tween::ACCEL_20_DECEL_100);
gfx::Transform transform;
// Translate y further down so that it would not interfere with the currently
// shown `icon_view()`.
transform.Translate((icon_view_bounds.x() - large_image_bounds.x()),
(icon_view_bounds.y() - large_image_bounds.y() +
large_image_bounds.height()));
transform.Scale(static_cast<double>(icon_view()->width()) /
image_container_view()->width(),
static_cast<double>(icon_view()->height()) /
image_container_view()->height());
views::AnimationBuilder()
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.Once()
.At(base::TimeDelta())
.SetTransform(image_container_view(), gfx::Transform())
.Then()
.SetDuration(base::Milliseconds(kLargeImageScaleDownDurationMs))
.SetTransform(image_container_view(), transform,
gfx::Tween::ACCEL_20_DECEL_100);
}
void AshNotificationView::PerformToggleInlineSettingsAnimation(
bool should_show_inline_settings) {
if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION)
return;
message_center_utils::InitLayerForAnimations(main_right_view_);
message_center_utils::InitLayerForAnimations(inline_settings_row());
// Fade out views.
if (should_show_inline_settings) {
// Fade out left_content if it's visible.
if (left_content_->GetVisible()) {
message_center_utils::InitLayerForAnimations(left_content());
message_center_utils::FadeOutView(
left_content(),
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* left_content) {
if (parent) {
left_content->layer()->SetOpacity(1.0f);
left_content->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), left_content()),
/*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs);
}
message_center_utils::FadeOutView(
expand_button_,
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* expand_button) {
if (parent) {
expand_button->layer()->SetOpacity(1.0f);
expand_button->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), expand_button_),
/*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs);
// Fade out icon_view() if it exists.
if (icon_view()) {
message_center_utils::InitLayerForAnimations(icon_view());
message_center_utils::FadeOutView(
icon_view(),
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* icon_view) {
if (parent) {
icon_view->layer()->SetOpacity(1.0f);
icon_view->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), icon_view()),
/*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs);
}
} else {
message_center_utils::FadeOutView(
inline_settings_row(),
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* inline_settings_row) {
if (parent) {
inline_settings_row->layer()->SetOpacity(1.0f);
inline_settings_row->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), inline_settings_row()),
/*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs);
if (!header_row()->GetVisible()) {
message_center_utils::FadeOutView(
header_row(),
base::BindRepeating(
[](base::WeakPtr<ash::AshNotificationView> parent,
views::View* header_row) {
if (parent) {
header_row->layer()->SetOpacity(1.0f);
header_row->SetVisible(false);
}
},
weak_factory_.GetWeakPtr(), header_row()),
/*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs);
}
}
// Fade in views.
message_center_utils::FadeInView(main_right_view_,
kToggleInlineSettingsFadeInDelayMs,
kToggleInlineSettingsFadeInDurationMs);
}
int AshNotificationView::CalculateMaxHeightForGroupedNotifications() {
auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
const WorkAreaInsets* work_area =
WorkAreaInsets::ForWindow(shelf->GetWindow()->GetRootWindow());
const int bottom = shelf->IsHorizontalAlignment()
? shelf->GetShelfBoundsInScreen().y()
: work_area->user_work_area_bounds().bottom();
const int free_space_height_above_anchor =
bottom - work_area->user_work_area_bounds().y();
const int vertical_margin = 2 * message_center::kMarginBetweenPopups +
kNotificationViewPadding.height();
return free_space_height_above_anchor -
control_buttons_container_->bounds().height() -
main_view_->bounds().height() - vertical_margin;
}
} // namespace ash