// Copyright 2024 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/notification_center/views/message_view_container.h"
#include "ash/constants/ash_features.h"
#include "ash/system/notification_center/message_center_constants.h"
#include "ash/system/notification_center/message_center_utils.h"
#include "ash/system/notification_center/metrics_utils.h"
#include "ash/system/notification_center/notification_style_utils.h"
#include "ash/system/notification_center/views/notification_list_view.h"
#include "ash/system/notification_center/views/notification_swipe_control_view.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/views/message_view.h"
#include "ui/views/background.h"
#include "ui/views/layout/fill_layout.h"
namespace ash {
namespace {
constexpr int kNotificationOuterCornerRadius =
kMessageCenterScrollViewCornerRadius;
constexpr int kNotificationInnerCornerRadius =
kMessageCenterNotificationInnerCornerRadius;
} // namespace
MessageViewContainer::MessageViewContainer(
std::unique_ptr<message_center::MessageView> message_view,
NotificationListView* list_view)
: list_view_(list_view) {
SetLayoutManager(std::make_unique<views::FillLayout>());
control_view_ = AddChildView(
std::make_unique<NotificationSwipeControlView>(message_view.get()));
// Load `MessageView` expand state.
message_center::ExpandState expand_state =
message_center::MessageCenter::Get()->GetNotificationExpandState(
message_view->notification_id());
if (expand_state != message_center::ExpandState::DEFAULT) {
message_view->SetExpanded(expand_state ==
message_center::ExpandState::USER_EXPANDED);
}
message_view_ = AddChildView(std::move(message_view));
message_view_->AddObserver(this);
}
int MessageViewContainer::CalculateHeight() const {
return message_view_ ? message_view_->GetHeightForWidth(
GetNotificationInMessageCenterWidth())
: 0;
}
void MessageViewContainer::UpdateBorder(const bool is_top,
const bool is_bottom,
const bool force_update) {
if (is_top_ == is_top && is_bottom_ == is_bottom && !force_update) {
return;
}
is_top_ = is_top;
is_bottom_ = is_bottom;
int top_radius =
is_top ? kNotificationOuterCornerRadius : kNotificationInnerCornerRadius;
int bottom_radius = is_bottom ? kNotificationOuterCornerRadius
: kNotificationInnerCornerRadius;
message_view_->UpdateCornerRadius(top_radius, bottom_radius);
// Custom notifications handle their background separately.
if (disable_default_background_) {
return;
}
message_view_->SetBackground(
notification_style_utils::CreateNotificationBackground(
top_radius, bottom_radius, /*is_popup_notification=*/false,
/*is_grouped_child_notification=*/false));
}
const std::string MessageViewContainer::GetNotificationId() const {
return message_view_->notification_id();
}
void MessageViewContainer::UpdateWithNotification(
const message_center::Notification& notification) {
message_view_->UpdateWithNotification(notification);
}
base::TimeDelta MessageViewContainer::GetBoundsAnimationDuration() const {
auto* notification =
message_center::MessageCenter::Get()->FindNotificationById(
message_view()->notification_id());
if (!notification) {
return base::Milliseconds(0);
}
return message_view()->GetBoundsAnimationDuration(*notification);
}
void MessageViewContainer::SetExpandedBySystem(bool expanded) {
base::AutoReset<bool> scoped_reset(&expanding_by_system_, true);
message_view_->SetExpanded(expanded);
}
void MessageViewContainer::SlideOutAndClose() {
is_slid_out_programatically_ = true;
message_view_->SlideOutAndClose(/*direction=*/1);
}
void MessageViewContainer::CloseSwipeControl() {
message_view_->CloseSwipeControl();
}
void MessageViewContainer::TriggerPreferredSizeChangedForAnimation() {
views::View::PreferredSizeChanged();
}
gfx::Size MessageViewContainer::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
if (list_view_ && list_view_->IsAnimatingExpandOrCollapseContainer(this)) {
// Width should never change, only height.
return gfx::Size(GetNotificationInMessageCenterWidth(),
gfx::Tween::IntValueBetween(
list_view_->GetCurrentAnimationValue(),
start_bounds_.height(), target_bounds_.height()));
}
return gfx::Size(GetNotificationInMessageCenterWidth(), CalculateHeight());
}
void MessageViewContainer::ChildPreferredSizeChanged(views::View* child) {
// If we've already been removed, ignore new child size changes.
if (is_removed_) {
return;
}
// PreferredSizeChanged will trigger
// NotificationListView::ChildPreferredSizeChanged.
absl::Cleanup defer_preferred_size_changed = [this] {
PreferredSizeChanged();
};
// Ignore non-user triggered expand/collapses.
if (expanding_by_system_) {
return;
}
auto* notification =
message_center::MessageCenter::Get()->FindNotificationById(
message_view()->notification_id());
if (!notification) {
return;
}
needs_bounds_animation_ = true;
}
void MessageViewContainer::OnSlideChanged(const std::string& notification_id) {
control_view_->UpdateButtonsVisibility();
if (notification_id != GetNotificationId() || !list_view_ ||
message_view_->GetSlideAmount() == 0 || !need_update_corner_radius_) {
return;
}
need_update_corner_radius_ = false;
previous_is_top_ = is_top_;
previous_is_bottom_ = is_bottom_;
UpdateBorder(/*is_top=*/true, /*is_bottom=*/true);
size_t index = list_view_->GetIndexOf(this).value();
// Also update the corner radius for the views above and below when sliding.
auto list_child_views = list_view_->children();
auto* above_view =
(index == 0)
? nullptr
: static_cast<MessageViewContainer*>(list_child_views[index - 1]);
auto* below_view =
(index == list_child_views.size() - 1)
? nullptr
: static_cast<MessageViewContainer*>(list_child_views[index + 1]);
if (above_view) {
above_view->UpdateBorder(above_view->is_top(),
/*is_bottom=*/true);
}
if (below_view) {
below_view->UpdateBorder(/*is_top=*/true, below_view->is_bottom());
}
}
void MessageViewContainer::OnSlideEnded(const std::string& notification_id) {
if (notification_id != GetNotificationId() || !list_view_) {
return;
}
std::optional<size_t> index = list_view_->GetIndexOf(this);
if (!index.has_value()) {
return;
}
// Also update the corner radius for the views above and below when sliding.
auto list_child_views = list_view_->children();
auto* above_view = (index == size_t{0})
? nullptr
: static_cast<MessageViewContainer*>(
list_child_views[index.value() - 1]);
auto* below_view = (index == list_child_views.size() - 1)
? nullptr
: static_cast<MessageViewContainer*>(
list_child_views[index.value() + 1]);
// Reset the corner radius of views to their normal state.
UpdateBorder(previous_is_top_, previous_is_bottom_);
set_need_update_corner_radius(true);
if (above_view && !above_view->is_slid_out()) {
above_view->UpdateBorder(above_view->is_top(), /*is_bottom=*/false);
above_view->set_need_update_corner_radius(true);
}
if (below_view && !below_view->is_slid_out()) {
below_view->UpdateBorder(/*is_top=*/false, below_view->is_bottom());
below_view->set_need_update_corner_radius(true);
}
}
void MessageViewContainer::OnPreSlideOut(const std::string& notification_id) {
if (!is_slid_out_programatically_) {
metrics_utils::LogClosedByUser(notification_id, /*is_swipe=*/true,
/*is_popup=*/false);
}
}
void MessageViewContainer::OnSlideOut(const std::string& notification_id) {
is_slid_out_ = true;
set_is_removed(true);
if (list_view_) {
list_view_->OnNotificationSlidOut();
}
}
bool MessageViewContainer::IsPinned() const {
return message_view_->GetMode() == message_center::MessageView::Mode::PINNED;
}
bool MessageViewContainer::IsGroupParent() const {
return message_center::MessageCenter::Get()
->FindNotificationById(GetNotificationId())
->group_parent();
}
BEGIN_METADATA(MessageViewContainer);
END_METADATA
} // namespace ash