Commit 474c1c5b authored by Tetsui Ohkubo's avatar Tetsui Ohkubo Committed by Commit Bot

NewMessageListView: Implement Clear All animation

This CL implements animation for clearing all Notifications.

This CL does not accurately implements the animation as the spec. For
the UX spec, see the bug.

TEST=UnifiedMessageListViewTest
BUG=897915

Change-Id: I750b3a3e9d86706bef7c9cd13474dbff986486e9
Reviewed-on: https://chromium-review.googlesource.com/c/1312176Reviewed-by: default avatarYoshiki Iguchi <yoshiki@chromium.org>
Commit-Queue: Tetsui Ohkubo <tetsui@chromium.org>
Cr-Commit-Position: refs/heads/master@{#609203}
parent 88668ea0
......@@ -216,13 +216,7 @@ void UnifiedMessageCenterView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
base::RecordAction(
base::UserMetricsAction("StatusArea_Notifications_ClearAll"));
// TODO(tetsui): Support Clear All animation.
message_list_view_->set_enable_animation(false);
message_center::MessageCenter::Get()->RemoveAllNotifications(
true /* by_user */,
message_center::MessageCenter::RemoveType::NON_PINNED);
message_list_view_->set_enable_animation(true);
message_list_view_->ClearAllWithAnimation();
}
void UnifiedMessageCenterView::OnWillChangeFocus(views::View* before,
......
......@@ -19,7 +19,6 @@ class ScrollView;
namespace ash {
class MessageCenterScrollBar;
class StackingNotificationCounterView;
class UnifiedSystemTrayModel;
class UnifiedSystemTrayView;
......@@ -40,7 +39,6 @@ class StackingNotificationCounterView : public views::View {
};
// Manages scrolling of notification list.
// TODO(tetsui): Rename to UnifiedMessageCenterView after old code is removed.
class ASH_EXPORT UnifiedMessageCenterView
: public views::View,
public MessageCenterScrollBar::Observer,
......@@ -61,6 +59,9 @@ class ASH_EXPORT UnifiedMessageCenterView
// UnifiedMessageListView.
void ConfigureMessageView(message_center::MessageView* message_view);
// Count number of notifications that are above visible area.
int GetStackedNotificationCount() const;
// views::View:
void AddedToWidget() override;
void RemovedFromWidget() override;
......@@ -93,9 +94,6 @@ class ASH_EXPORT UnifiedMessageCenterView
// TopCornerBorder.
void NotifyHeightBelowScroll();
// Count number of notifications that are above visible area.
int GetStackedNotificationCount() const;
UnifiedSystemTrayView* const parent_;
StackingNotificationCounterView* const stacking_counter_;
MessageCenterScrollBar* const scroll_bar_;
......
......@@ -26,6 +26,10 @@ namespace {
constexpr base::TimeDelta kClosingAnimationDuration =
base::TimeDelta::FromMilliseconds(330);
constexpr base::TimeDelta kClearAllStackedAnimationDuration =
base::TimeDelta::FromMilliseconds(40);
constexpr base::TimeDelta kClearAllVisibleAnimationDuration =
base::TimeDelta::FromMilliseconds(160);
} // namespace
......@@ -101,6 +105,11 @@ class UnifiedMessageListView::MessageViewContainer
void CloseSwipeControl() { message_view_->CloseSwipeControl(); }
// Returns if the notification is pinned i.e. can be removed manually.
bool IsPinned() const {
return message_view_->GetMode() == MessageView::Mode::PINNED;
}
// views::View:
void ChildPreferredSizeChanged(views::View* child) override {
PreferredSizeChanged();
......@@ -151,7 +160,6 @@ UnifiedMessageListView::UnifiedMessageListView(
model_(model),
animation_(std::make_unique<gfx::LinearAnimation>(this)) {
MessageCenter::Get()->AddObserver(this);
animation_->SetDuration(kClosingAnimationDuration);
animation_->SetCurrentValue(1.0);
}
......@@ -177,6 +185,24 @@ void UnifiedMessageListView::Init() {
UpdateBounds();
}
void UnifiedMessageListView::ClearAllWithAnimation() {
if (state_ == State::CLEAR_ALL_STACKED || state_ == State::CLEAR_ALL_VISIBLE)
return;
ResetBounds();
{
base::AutoReset<bool> auto_reset(&ignore_notification_remove_, true);
message_center::MessageCenter::Get()->RemoveAllNotifications(
true /* by_user */,
message_center::MessageCenter::RemoveType::NON_PINNED);
}
state_ = State::CLEAR_ALL_STACKED;
UpdateClearAllAnimation();
if (state_ != State::IDLE)
StartAnimation();
}
int UnifiedMessageListView::GetLastNotificationHeight() const {
if (!has_children())
return 0;
......@@ -222,6 +248,8 @@ void UnifiedMessageListView::OnNotificationAdded(const std::string& id) {
if (!notification)
return;
InterruptClearAll();
// Collapse all notifications before adding new one.
CollapseAllNotifications();
......@@ -235,6 +263,9 @@ void UnifiedMessageListView::OnNotificationAdded(const std::string& id) {
void UnifiedMessageListView::OnNotificationRemoved(const std::string& id,
bool by_user) {
if (ignore_notification_remove_)
return;
InterruptClearAll();
ResetBounds();
for (int i = 0; i < child_count(); ++i) {
......@@ -245,16 +276,10 @@ void UnifiedMessageListView::OnNotificationRemoved(const std::string& id,
}
}
if (!enable_animation_) {
ResetBounds();
return;
}
UpdateBorders();
UpdateBounds();
state_ = State::SLIDE_OUT;
animation_->Start();
StartAnimation();
}
void UnifiedMessageListView::OnNotificationUpdated(const std::string& id) {
......@@ -262,6 +287,8 @@ void UnifiedMessageListView::OnNotificationUpdated(const std::string& id) {
if (!notification)
return;
InterruptClearAll();
for (int i = 0; i < child_count(); ++i) {
auto* view = GetContainer(i);
if (view->GetNotificationId() == id) {
......@@ -292,14 +319,19 @@ void UnifiedMessageListView::AnimationEnded(const gfx::Animation* animation) {
if (state_ == State::SLIDE_OUT) {
DeleteRemovedNotifications();
UpdateBorders();
UpdateBounds();
state_ = State::MOVE_DOWN;
animation_->Start();
} else if (state_ == State::MOVE_DOWN) {
state_ = State::IDLE;
} else if (state_ == State::CLEAR_ALL_STACKED ||
state_ == State::CLEAR_ALL_VISIBLE) {
DeleteRemovedNotifications();
UpdateClearAllAnimation();
}
if (state_ != State::IDLE)
StartAnimation();
}
void UnifiedMessageListView::AnimationProgressed(
......@@ -321,6 +353,10 @@ MessageView* UnifiedMessageListView::CreateMessageView(
return view;
}
int UnifiedMessageListView::GetStackedNotificationCount() const {
return message_center_view_->GetStackedNotificationCount();
}
UnifiedMessageListView::MessageViewContainer*
UnifiedMessageListView::GetContainer(int index) {
return const_cast<MessageViewContainer*>(
......@@ -332,6 +368,16 @@ UnifiedMessageListView::GetContainer(int index) const {
return static_cast<const MessageViewContainer*>(child_at(index));
}
UnifiedMessageListView::MessageViewContainer*
UnifiedMessageListView::GetNextRemovableNotification() {
for (int i = 0; i < child_count(); ++i) {
auto* view = GetContainer(i);
if (!view->IsPinned())
return view;
}
return nullptr;
}
void UnifiedMessageListView::CollapseAllNotifications() {
base::AutoReset<bool> auto_reset(&ignore_size_change_, true);
for (int i = 0; i < child_count(); ++i)
......@@ -365,7 +411,6 @@ void UnifiedMessageListView::UpdateBounds() {
void UnifiedMessageListView::ResetBounds() {
DeleteRemovedNotifications();
UpdateBorders();
UpdateBounds();
state_ = State::IDLE;
......@@ -375,6 +420,19 @@ void UnifiedMessageListView::ResetBounds() {
PreferredSizeChanged();
}
void UnifiedMessageListView::InterruptClearAll() {
if (state_ != State::CLEAR_ALL_STACKED && state_ != State::CLEAR_ALL_VISIBLE)
return;
for (int i = 0; i < child_count(); ++i) {
auto* view = GetContainer(i);
if (!view->IsPinned())
view->set_is_removed();
}
DeleteRemovedNotifications();
}
void UnifiedMessageListView::DeleteRemovedNotifications() {
std::vector<MessageViewContainer*> removed_views;
for (int i = 0; i < child_count(); ++i) {
......@@ -387,13 +445,70 @@ void UnifiedMessageListView::DeleteRemovedNotifications() {
model_->RemoveNotificationExpanded(view->GetNotificationId());
delete view;
}
UpdateBorders();
}
void UnifiedMessageListView::StartAnimation() {
DCHECK_NE(state_, State::IDLE);
switch (state_) {
case State::IDLE:
break;
case State::SLIDE_OUT:
FALLTHROUGH;
case State::MOVE_DOWN:
animation_->SetDuration(kClosingAnimationDuration);
animation_->Start();
break;
case State::CLEAR_ALL_STACKED:
animation_->SetDuration(kClearAllStackedAnimationDuration);
animation_->Start();
break;
case State::CLEAR_ALL_VISIBLE:
animation_->SetDuration(kClearAllVisibleAnimationDuration);
animation_->Start();
break;
}
}
void UnifiedMessageListView::UpdateClearAllAnimation() {
DCHECK(state_ == State::CLEAR_ALL_STACKED ||
state_ == State::CLEAR_ALL_VISIBLE);
auto* view = GetNextRemovableNotification();
if (view)
view->set_is_removed();
if (state_ == State::CLEAR_ALL_STACKED) {
if (view && GetStackedNotificationCount() > 0) {
DeleteRemovedNotifications();
UpdateBounds();
start_height_ = ideal_height_;
PreferredSizeChanged();
state_ = State::CLEAR_ALL_STACKED;
} else {
state_ = State::CLEAR_ALL_VISIBLE;
}
}
if (state_ == State::CLEAR_ALL_VISIBLE) {
UpdateBounds();
if (view || start_height_ != ideal_height_)
state_ = State::CLEAR_ALL_VISIBLE;
else
state_ = State::IDLE;
}
}
double UnifiedMessageListView::GetCurrentValue() const {
return gfx::Tween::CalculateValue(state_ == State::SLIDE_OUT
? gfx::Tween::EASE_IN
: gfx::Tween::FAST_OUT_SLOW_IN,
animation_->GetCurrentValue());
return gfx::Tween::CalculateValue(
state_ == State::SLIDE_OUT || state_ == State::CLEAR_ALL_VISIBLE
? gfx::Tween::EASE_IN
: gfx::Tween::FAST_OUT_SLOW_IN,
animation_->GetCurrentValue());
}
} // namespace ash
......@@ -42,6 +42,10 @@ class ASH_EXPORT UnifiedMessageListView
// after ctor.
void Init();
// Starts Clear All animation and removes all notifications. Notifications are
// removed from MessageCenter at the beginning of the animation.
void ClearAllWithAnimation();
// Get the height of the notification at the bottom. If no notification is
// added, it returns 0.
int GetLastNotificationHeight() const;
......@@ -69,15 +73,14 @@ class ASH_EXPORT UnifiedMessageListView
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationCanceled(const gfx::Animation* animation) override;
void set_enable_animation(bool enable_animation) {
enable_animation_ = enable_animation;
}
protected:
// Virtual for testing.
virtual message_center::MessageView* CreateMessageView(
const message_center::Notification& notification);
// Virtual for testing.
virtual int GetStackedNotificationCount() const;
private:
friend class UnifiedMessageCenterViewTest;
friend class UnifiedMessageListViewTest;
......@@ -94,12 +97,22 @@ class ASH_EXPORT UnifiedMessageListView
SLIDE_OUT,
// Moving down notifications.
MOVE_DOWN
MOVE_DOWN,
// Part 1 of Clear All animation. Removing all hidden notifications above
// the visible area.
CLEAR_ALL_STACKED,
// Part 2 of Clear All animation. Removing all visible notifications.
CLEAR_ALL_VISIBLE
};
MessageViewContainer* GetContainer(int index);
const MessageViewContainer* GetContainer(int index) const;
// Returns the first removable notification from the top.
MessageViewContainer* GetNextRemovableNotification();
// Current progress of the animation between 0.0 and 1.0. Returns 1.0 when
// it's not animating.
double GetCurrentValue() const;
......@@ -120,9 +133,19 @@ class ASH_EXPORT UnifiedMessageListView
// |final_bounds|.
void ResetBounds();
// Interrupts clear all animation and deletes all the remaining notifications.
// ResetBounds() should be called after that.
void InterruptClearAll();
// Deletes all the MessageViewContainer marked as |is_removed|.
void DeleteRemovedNotifications();
// Starts the animation for current |state_|.
void StartAnimation();
// Updates the state between each Clear All animation phase.
void UpdateClearAllAnimation();
UnifiedMessageCenterView* const message_center_view_;
UnifiedSystemTrayModel* const model_;
......@@ -131,6 +154,10 @@ class ASH_EXPORT UnifiedMessageListView
// multiple times because of sequential SetExpanded() calls.
bool ignore_size_change_ = false;
// If true, OnNotificationRemoved() will be ignored. Used in
// ClearAllWithAnimation().
bool ignore_notification_remove_ = false;
// Manages notification closing animation. UnifiedMessageListView does not use
// implicit animation.
const std::unique_ptr<gfx::LinearAnimation> animation_;
......@@ -145,9 +172,6 @@ class ASH_EXPORT UnifiedMessageListView
// as height().
int ideal_height_ = 0;
// If false, disables animation on notification removal.
bool enable_animation_ = true;
DISALLOW_COPY_AND_ASSIGN(UnifiedMessageListView);
};
......
......@@ -52,6 +52,10 @@ class TestUnifiedMessageListView : public UnifiedMessageListView {
~TestUnifiedMessageListView() override = default;
void set_stacked_notification_count(int stacked_notification_count) {
stacked_notification_count_ = stacked_notification_count;
}
// UnifiedMessageListView:
message_center::MessageView* CreateMessageView(
const message_center::Notification& notification) override {
......@@ -60,7 +64,13 @@ class TestUnifiedMessageListView : public UnifiedMessageListView {
return view;
}
int GetStackedNotificationCount() const override {
return stacked_notification_count_;
}
private:
int stacked_notification_count_ = 0;
DISALLOW_COPY_AND_ASSIGN(TestUnifiedMessageListView);
};
......@@ -92,14 +102,16 @@ class UnifiedMessageListViewTest : public AshTestBase,
}
protected:
std::string AddNotification() {
std::string AddNotification(bool pinned = false) {
std::string id = base::IntToString(id_++);
MessageCenter::Get()->AddNotification(std::make_unique<Notification>(
auto notification = std::make_unique<Notification>(
message_center::NOTIFICATION_TYPE_BASE_FORMAT, id,
base::UTF8ToUTF16("test title"), base::UTF8ToUTF16("test message"),
gfx::Image(), base::string16() /* display_source */, GURL(),
message_center::NotifierId(), message_center::RichNotificationData(),
new message_center::NotificationDelegate()));
new message_center::NotificationDelegate());
notification->set_pinned(pinned);
MessageCenter::Get()->AddNotification(std::move(notification));
return id;
}
......@@ -144,7 +156,7 @@ class UnifiedMessageListViewTest : public AshTestBase,
int GetMessageViewCount() { return message_list_view()->child_count(); }
UnifiedMessageListView* message_list_view() const {
TestUnifiedMessageListView* message_list_view() const {
return message_list_view_.get();
}
......@@ -319,16 +331,6 @@ TEST_F(UnifiedMessageListViewTest, RemovingNotificationAnimation) {
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
}
TEST_F(UnifiedMessageListViewTest, DisableAnimation) {
auto id0 = AddNotification();
auto id1 = AddNotification();
CreateMessageListView();
message_list_view()->set_enable_animation(false);
MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
EXPECT_FALSE(IsAnimating());
}
TEST_F(UnifiedMessageListViewTest, ResetAnimation) {
auto id0 = AddNotification();
auto id1 = AddNotification();
......@@ -387,4 +389,127 @@ TEST_F(UnifiedMessageListViewTest, KeepManuallyExpanded) {
EXPECT_FALSE(GetMessageViewAt(2)->IsManuallyExpandedOrCollapsed());
}
TEST_F(UnifiedMessageListViewTest, ClearAllWithOnlyVisibleNotifications) {
AddNotification();
AddNotification();
CreateMessageListView();
EXPECT_EQ(2, message_list_view()->child_count());
int previous_height = message_list_view()->GetPreferredSize().height();
gfx::Rect previous_bounds = GetMessageViewBounds(0);
message_list_view()->ClearAllWithAnimation();
AnimateToMiddle();
EXPECT_LT(previous_bounds.x(), GetMessageViewBounds(0).x());
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
AnimateToEnd();
EXPECT_EQ(1, message_list_view()->child_count());
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
previous_bounds = GetMessageViewBounds(0);
AnimateToMiddle();
EXPECT_LT(previous_bounds.x(), GetMessageViewBounds(0).x());
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
EXPECT_EQ(0, message_list_view()->child_count());
AnimateToMiddle();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
EXPECT_TRUE(MessageCenter::Get()->GetVisibleNotifications().empty());
EXPECT_FALSE(IsAnimating());
}
TEST_F(UnifiedMessageListViewTest, ClearAllWithStackingNotifications) {
AddNotification();
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->set_stacked_notification_count(2);
EXPECT_EQ(3, message_list_view()->child_count());
message_list_view()->ClearAllWithAnimation();
EXPECT_EQ(2, message_list_view()->child_count());
message_list_view()->set_stacked_notification_count(1);
int previous_height = message_list_view()->GetPreferredSize().height();
AnimateToMiddle();
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
AnimateToEnd();
EXPECT_EQ(1, message_list_view()->child_count());
message_list_view()->set_stacked_notification_count(0);
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToMiddle();
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
AnimateToEnd();
EXPECT_EQ(1, message_list_view()->child_count());
gfx::Rect previous_bounds = GetMessageViewBounds(0);
AnimateToMiddle();
EXPECT_LT(previous_bounds.x(), GetMessageViewBounds(0).x());
AnimateToEnd();
EXPECT_EQ(0, message_list_view()->child_count());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToMiddle();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToEnd();
EXPECT_EQ(0, message_list_view()->child_count());
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
EXPECT_FALSE(IsAnimating());
}
TEST_F(UnifiedMessageListViewTest, ClearAllClosedInTheMiddle) {
AddNotification();
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->ClearAllWithAnimation();
AnimateToMiddle();
DestroyMessageListView();
EXPECT_TRUE(MessageCenter::Get()->GetVisibleNotifications().empty());
}
TEST_F(UnifiedMessageListViewTest, ClearAllInterrupted) {
AddNotification();
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->ClearAllWithAnimation();
AnimateToMiddle();
auto new_id = AddNotification();
EXPECT_EQ(1u, MessageCenter::Get()->GetVisibleNotifications().size());
EXPECT_TRUE(MessageCenter::Get()->FindVisibleNotificationById(new_id));
}
TEST_F(UnifiedMessageListViewTest, ClearAllWithPinnedNotifications) {
AddNotification(true /* pinned */);
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->ClearAllWithAnimation();
AnimateUntilIdle();
EXPECT_EQ(1, message_list_view()->child_count());
}
} // namespace ash
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment