chromium/ash/system/notification_center/views/ash_notification_view.h

// Copyright 2021 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_NOTIFICATION_CENTER_VIEWS_ASH_NOTIFICATION_VIEW_H_
#define ASH_SYSTEM_NOTIFICATION_CENTER_VIEWS_ASH_NOTIFICATION_VIEW_H_

#include <vector>

#include "ash/ash_export.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_observer.h"
#include "ui/message_center/views/notification_input_container.h"
#include "ui/message_center/views/notification_view.h"
#include "ui/message_center/views/notification_view_base.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"

namespace message_center {
class Notification;
}  // namespace message_center

namespace views {
class BoxLayout;
class LabelButton;
class View;
}  // namespace views

namespace ash {

class RoundedImageView;
class AshNotificationExpandButton;
class IconButton;
class TimestampView;

// Customized NotificationView for notification on ChromeOS. This view is used
// to displays all current types of notification on ChromeOS (web, basic, image,
// and list) except custom notification.
class ASH_EXPORT AshNotificationView
    : public message_center::NotificationViewBase,
      public message_center::MessageCenterObserver,
      public views::WidgetObserver {
  METADATA_HEADER(AshNotificationView, message_center::NotificationViewBase)

 public:
  // TODO(crbug/1241983): Add metadata and builder support to this view.
  explicit AshNotificationView(const message_center::Notification& notification,
                               bool shown_in_popup);
  AshNotificationView(const AshNotificationView&) = delete;
  AshNotificationView& operator=(const AshNotificationView&) = delete;
  ~AshNotificationView() override;

  // Toggle the expand state of the notification. This function should only be
  // used to handle user manually expand/collapse a notification.
  void ToggleExpand();

  // Called when a child notificaiton's preferred size changes.
  void GroupedNotificationsPreferredSizeChanged();

  // Drag related functions ----------------------------------------------------

  // Returns the bounds of the area where the drag can be initiated. The
  // returned bounds are in `AshNotificationView` local coordinates. Returns
  // `std::nullopt` if the notification view is not draggable.
  std::optional<gfx::Rect> GetDragAreaBounds() const;

  // Returns the drag image shown when the ash notification is under drag.
  // Returns `std::nullopt` if the notification view is not draggable.
  std::optional<gfx::ImageSkia> GetDragImage();

  // Attaches the drop data. This method should be called only if this
  // notification view is draggable.
  void AttachDropData(ui::OSExchangeData* data);

  // Returns true if this notification view is draggable.
  bool IsDraggable() const;

  // message_center::MessageView:
  void AnimateGroupedChildExpandedCollapse(bool expanded) override;
  void AnimateSingleToGroup(const std::string& notification_id,
                            std::string parent_id) override;
  void AddGroupNotification(
      const message_center::Notification& notification) override;
  void PopulateGroupNotifications(
      const std::vector<const message_center::Notification*>& notifications)
      override;
  void RemoveGroupNotification(const std::string& notification_id) override;
  void SetGroupedChildExpanded(bool expanded) override;
  // Called after `PreferredSizeChanged()`, so the current state is the target
  // state.
  base::TimeDelta GetBoundsAnimationDuration(
      const message_center::Notification& notification) const override;

  // message_center::NotificationViewBase:
  void AddedToWidget() override;
  void Layout(PassKey) override;
  void UpdateViewForExpandedState(bool expanded) override;
  void UpdateWithNotification(
      const message_center::Notification& notification) override;
  void CreateOrUpdateHeaderView(
      const message_center::Notification& notification) override;
  void CreateOrUpdateTitleView(
      const message_center::Notification& notification) override;
  void CreateOrUpdateSmallIconView(
      const message_center::Notification& notification) override;
  void CreateOrUpdateInlineSettingsViews(
      const message_center::Notification& notification) override;
  void CreateOrUpdateSnoozeSettingsViews(
      const message_center::Notification& notification) override;
  void CreateOrUpdateCompactTitleMessageView(
      const message_center::Notification& notification) override;
  void CreateOrUpdateProgressViews(
      const message_center::Notification& notification) override;
  void UpdateControlButtonsVisibility() override;
  bool IsIconViewShown() const override;
  void SetExpandButtonVisibility(bool visible) override;
  bool IsExpandable() const override;
  void UpdateCornerRadius(int top_radius, int bottom_radius) override;
  void OnThemeChanged() override;
  std::unique_ptr<message_center::NotificationInputContainer>
  GenerateNotificationInputContainer() override;
  std::unique_ptr<views::LabelButton> GenerateNotificationLabelButton(
      views::Button::PressedCallback callback,
      const std::u16string& label) override;
  gfx::Size GetIconViewSize() const override;
  int GetLargeImageViewMaxWidth() const override;
  void ToggleInlineSettings(const ui::Event& event) override;
  void ToggleSnoozeSettings(const ui::Event& event) override;
  void OnInlineReplyUpdated() override;
  views::View* FindGroupNotificationView(
      const std::string& notification_id) override;

  void set_is_animating(bool is_animating) { is_animating_ = is_animating; }
  bool is_animating() { return is_animating_; }

  AshNotificationExpandButton* expand_button_for_test() {
    return expand_button_;
  }

  message_center::NotificationControlButtonsView*
  control_buttons_view_for_test() {
    return control_buttons_view_;
  }

  std::vector<raw_ptr<views::LabelButton, VectorExperimental>>
  GetActionButtonsForTest();

  views::Label* GetTitleRowLabelForTest();

  message_center::NotificationInputContainer* GetInlineReplyForTest();

  // View containing all grouped notifications, propagates size changes
  // to the parent notification view.
  class GroupedNotificationsContainer : public views::BoxLayoutView {
    METADATA_HEADER(GroupedNotificationsContainer, views::BoxLayoutView)

   public:
    GroupedNotificationsContainer() = default;
    GroupedNotificationsContainer(const GroupedNotificationsContainer&) =
        delete;
    GroupedNotificationsContainer& operator=(
        const GroupedNotificationsContainer&) = delete;
    void ChildPreferredSizeChanged(views::View* view) override;
    void SetParentNotificationView(
        AshNotificationView* parent_notification_view);

   private:
    raw_ptr<AshNotificationView> parent_notification_view_ = nullptr;
  };
  BEGIN_VIEW_BUILDER(/*no export*/,
                     GroupedNotificationsContainer,
                     views::BoxLayoutView)
  VIEW_BUILDER_PROPERTY(AshNotificationView*, ParentNotificationView)
  END_VIEW_BUILDER

 private:
  friend class AshNotificationViewTestBase;
  friend class MessageCenterMetricsUtilsTest;
  friend class NotificationGroupingControllerTest;

  // Customized title row for this notification view with added timestamp in
  // collapse mode.
  class NotificationTitleRow : public views::View {
    METADATA_HEADER(NotificationTitleRow, views::View)

   public:
    explicit NotificationTitleRow(const std::u16string& title);
    NotificationTitleRow(const NotificationTitleRow&) = delete;
    NotificationTitleRow& operator=(const NotificationTitleRow&) = delete;
    ~NotificationTitleRow() override;

    // Update title view's text.
    void UpdateTitle(const std::u16string& title);

    // Update the text for `timestamp_in_collapsed_view_`.
    void UpdateTimestamp(base::Time timestamp);

    // Update children's visibility based on the state of expand/collapse.
    void UpdateVisibility(bool in_collapsed_mode);

    // Perform expand/collapse animation in children views.
    void PerformExpandCollapseAnimation();

    // Set the maximum available width for this view.
    void SetMaxAvailableWidth(int max_available_width);

    // views::View:
    gfx::Size CalculatePreferredSize(
        const views::SizeBounds& available_size) const override;
    void OnThemeChanged() override;

    views::Label* title_view() { return title_view_; }

   private:
    friend class AshNotificationViewTestBase;
    // Showing notification title.
    const raw_ptr<views::Label> title_view_;

    // Timestamp view shown alongside the title in collapsed state.
    const raw_ptr<views::Label> title_row_divider_;
    const raw_ptr<TimestampView> timestamp_in_collapsed_view_;

    // The maximum width available to the title row.
    int max_available_width_ = 0;
  };

  // message_center::MessageCenterObserver:
  void OnNotificationRemoved(const std::string& notification_id,
                             bool by_user) override;

  // views::WidgetObserver:
  void OnWidgetClosing(views::Widget* widget) override;
  void OnWidgetDestroying(views::Widget* widget) override;

  // Abort all currently running layer animations. This includes any animatios
  // on child notifications for parent notification views.
  void AbortAllAnimations();

  // Create or update the customized snooze button in action buttons row
  // according to the given notification.
  void CreateOrUpdateSnoozeButton(
      const message_center::Notification& notification);

  // Update visibility for grouped notifications to ensure only
  // `kMaxGroupedNotificationsInCollapsedState` are visible in the collapsed
  // state.
  void UpdateGroupedNotificationsVisibility();

  // Update `message_in_expanded_view_` according to the given notification.
  void UpdateMessageLabelInExpandedState(
      const message_center::Notification& notification);

  // Get the available space for `message_label_in_expanded_state_` width.
  int GetExpandedMessageLabelWidth();

  // Update the color and icon for `app_icon_view_`.
  void UpdateAppIconView(const message_center::Notification* notification);

  // Update the color of icon and buttons.
  void UpdateIconAndButtonsColor(
      const message_center::Notification* notification);

  // Animate resizing a parent notification view after a child notification view
  // has been removed from itself.
  void AnimateResizeAfterRemoval(views::View* to_be_removed);

  // AshNotificationView will animate its expand/collapse in the parent's
  // ChildPreferredSizeChange(). Child views are animated here.
  void PerformExpandCollapseAnimation();

  // Expand/collapse animation for large image within `image_container_view()`.
  void PerformLargeImageAnimation();

  // Animations when toggle inline settings.
  void PerformToggleInlineSettingsAnimation(bool should_show_inline_settings);

  // Fade in animation when converting from single to group notification.
  void AnimateSingleToGroupFadeIn();

  // Calculate vertical space available on screen for the
  // grouped_notifications_scroll_view_
  int CalculateMaxHeightForGroupedNotifications();

  // Return true is `message_label()` is truncated. We need this helper because
  // Label::IsDisplayTextTruncated doesn't work when `message_label()` hasn't
  // been laid out yet.
  bool IsMessageLabelTruncated();

  // Attaches the large image's binary data as drop data. This method should be
  // called only if this notification view is draggable.
  void AttachBinaryImageAsDropData(ui::OSExchangeData* data);

  // Called when the fade out animation for `view` has ended. This function
  // resets the views's opacity to 1.0f and makes it invisible.
  void OnFadeOutAnimationEnded(views::View* view);

  // Called when the grouped animation for this view has ended, or has been
  // aborted.
  void OnGroupedAnimationEnded(views::View* left_content,
                               views::View* right_content,
                               views::View* message_label_in_expanded_state,
                               views::View* image_container_view,
                               views::View* action_buttons_row,
                               AshNotificationExpandButton* expand_button,
                               std::string notification_id,
                               std::string parent_id);

  // A helper wrapping `OnFadeOutAnimationEnded` for `view` as a closure.
  base::OnceClosure OnFadeOutAnimationEndedClosure(views::View* view);

  // A helper for grouped animations ending/aborting.
  base::OnceClosure OnGroupedAnimationEndedClosure(
      views::View* left_content,
      views::View* right_content,
      views::View* message_label_in_expanded_state,
      views::View* image_container_view,
      views::View* action_buttons_row,
      AshNotificationExpandButton* expand_button,
      const std::string& notification_id,
      std::string parent_id);

  // Owned by views hierarchy.
  raw_ptr<views::View> main_view_ = nullptr;
  raw_ptr<views::View> main_right_view_ = nullptr;
  raw_ptr<RoundedImageView> app_icon_view_ = nullptr;
  raw_ptr<AshNotificationExpandButton> expand_button_ = nullptr;
  raw_ptr<views::View> left_content_ = nullptr;
  raw_ptr<views::Label> message_label_in_expanded_state_ = nullptr;
  raw_ptr<views::ScrollView> grouped_notifications_scroll_view_ = nullptr;
  raw_ptr<views::View> grouped_notifications_container_ = nullptr;
  raw_ptr<views::View> collapsed_summary_view_ = nullptr;
  raw_ptr<message_center::NotificationControlButtonsView>
      control_buttons_view_ = nullptr;
  raw_ptr<views::View> snooze_button_spacer_ = nullptr;
  raw_ptr<IconButton> snooze_button_ = nullptr;

  // These views below are dynamically created inside view hierarchy.
  raw_ptr<NotificationTitleRow, DanglingUntriaged> title_row_ = nullptr;

  // Layout manager for the container of header and left content.
  raw_ptr<views::BoxLayout> header_left_content_layout_ = nullptr;

  // Corner radius of the notification view.
  int top_radius_ = 0;
  int bottom_radius_ = 0;

  // Count of grouped notifications contained in this view. Used for
  // modifying the visibility of the title and content views in the parent
  // notification as well as showing the number of grouped notifications not
  // shown in a collapsed grouped notification.
  int total_grouped_notifications_ = 0;

  // Cached background color id to avoid unnecessary update.
  ui::ColorId background_color_id_ = 0;

  // Used to prevent setting bounds in `AshNotificationView` while running
  // animations to resize this view.
  bool is_animating_ = false;

  // Whether the notification associated with this view is a parent or child
  // in a grouped notification. Used to update visibility of UI elements
  // specific to each type of notification.
  bool is_grouped_parent_view_ = false;
  bool is_grouped_child_view_ = false;

  // Whether this view is shown in a notification popup.
  bool shown_in_popup_ = false;

  base::ScopedObservation<message_center::MessageCenter, MessageCenterObserver>
      message_center_observer_{this};
  base::ScopedObservation<views::Widget, views::WidgetObserver>
      widget_observation_{this};

  base::WeakPtrFactory<AshNotificationView> weak_factory_{this};
};

}  // namespace ash

DEFINE_VIEW_BUILDER(/* no export */,
                    ash::AshNotificationView::GroupedNotificationsContainer)

#endif  // ASH_SYSTEM_NOTIFICATION_CENTER_VIEWS_ASH_NOTIFICATION_VIEW_H_