Commit 623c7e4e authored by mohsen's avatar mohsen Committed by Commit bot

Add ink drop ripple to shelf overflow button

It uses a flood fill ripple that remains active while the overflow shelf
is active. InkDropHostView and CustomButton are updated to handle active
case:
 - CustomButton does not hide the ripple if it is not a pending ripple;
 - InkDropHostView does not show or hide a pending ripple if it is
   showing an active ripple.

BUG=612579
TEST=OverflowButtonInkDropTest.* in ash_unittests, manual

Review-Url: https://codereview.chromium.org/2178163002
Cr-Commit-Position: refs/heads/master@{#419232}
parent 2ad909df
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "ash/common/shelf/overflow_bubble.h" #include "ash/common/shelf/overflow_bubble.h"
#include "ash/common/shelf/overflow_bubble_view.h" #include "ash/common/shelf/overflow_bubble_view.h"
#include "ash/common/shelf/overflow_button.h"
#include "ash/common/shelf/shelf_view.h" #include "ash/common/shelf/shelf_view.h"
#include "ash/common/shelf/wm_shelf.h" #include "ash/common/shelf/wm_shelf.h"
#include "ash/common/system/tray/tray_background_view.h" #include "ash/common/system/tray/tray_background_view.h"
...@@ -17,7 +18,7 @@ namespace ash { ...@@ -17,7 +18,7 @@ namespace ash {
OverflowBubble::OverflowBubble(WmShelf* wm_shelf) OverflowBubble::OverflowBubble(WmShelf* wm_shelf)
: wm_shelf_(wm_shelf), : wm_shelf_(wm_shelf),
bubble_(nullptr), bubble_(nullptr),
anchor_(nullptr), overflow_button_(nullptr),
shelf_view_(nullptr) { shelf_view_(nullptr) {
WmShell::Get()->AddPointerWatcher(this, WmShell::Get()->AddPointerWatcher(this,
views::PointerWatcherEventTypes::BASIC); views::PointerWatcherEventTypes::BASIC);
...@@ -28,50 +29,47 @@ OverflowBubble::~OverflowBubble() { ...@@ -28,50 +29,47 @@ OverflowBubble::~OverflowBubble() {
WmShell::Get()->RemovePointerWatcher(this); WmShell::Get()->RemovePointerWatcher(this);
} }
void OverflowBubble::Show(views::View* anchor, ShelfView* shelf_view) { void OverflowBubble::Show(OverflowButton* overflow_button,
DCHECK(anchor); ShelfView* shelf_view) {
DCHECK(overflow_button);
DCHECK(shelf_view); DCHECK(shelf_view);
Hide(); Hide();
bubble_ = new OverflowBubbleView(wm_shelf_); bubble_ = new OverflowBubbleView(wm_shelf_);
bubble_->InitOverflowBubble(anchor, shelf_view); bubble_->InitOverflowBubble(overflow_button, shelf_view);
shelf_view_ = shelf_view; shelf_view_ = shelf_view;
anchor_ = anchor; overflow_button_ = overflow_button;
TrayBackgroundView::InitializeBubbleAnimations(bubble_->GetWidget()); TrayBackgroundView::InitializeBubbleAnimations(bubble_->GetWidget());
bubble_->GetWidget()->AddObserver(this); bubble_->GetWidget()->AddObserver(this);
bubble_->GetWidget()->Show(); bubble_->GetWidget()->Show();
overflow_button->OnOverflowBubbleShown();
} }
void OverflowBubble::Hide() { void OverflowBubble::Hide() {
if (!IsShowing()) if (!IsShowing())
return; return;
OverflowButton* overflow_button = overflow_button_;
bubble_->GetWidget()->RemoveObserver(this); bubble_->GetWidget()->RemoveObserver(this);
bubble_->GetWidget()->Close(); bubble_->GetWidget()->Close();
bubble_ = NULL; bubble_ = nullptr;
anchor_ = NULL; overflow_button_ = nullptr;
shelf_view_ = NULL; shelf_view_ = nullptr;
}
void OverflowBubble::HideBubbleAndRefreshButton() {
if (!IsShowing())
return;
views::View* anchor = anchor_; overflow_button->OnOverflowBubbleHidden();
Hide();
// Update overflow button (|anchor|) status when overflow bubble is hidden
// by outside event of overflow button.
anchor->SchedulePaint();
} }
void OverflowBubble::ProcessPressedEvent( void OverflowBubble::ProcessPressedEvent(
const gfx::Point& event_location_in_screen) { const gfx::Point& event_location_in_screen) {
if (IsShowing() && !shelf_view_->IsShowingMenu() && if (IsShowing() && !shelf_view_->IsShowingMenu() &&
!bubble_->GetBoundsInScreen().Contains(event_location_in_screen) && !bubble_->GetBoundsInScreen().Contains(event_location_in_screen) &&
!anchor_->GetBoundsInScreen().Contains(event_location_in_screen)) { !overflow_button_->GetBoundsInScreen().Contains(
HideBubbleAndRefreshButton(); event_location_in_screen)) {
Hide();
} }
} }
...@@ -86,9 +84,9 @@ void OverflowBubble::OnPointerEventObserved( ...@@ -86,9 +84,9 @@ void OverflowBubble::OnPointerEventObserved(
void OverflowBubble::OnWidgetDestroying(views::Widget* widget) { void OverflowBubble::OnWidgetDestroying(views::Widget* widget) {
DCHECK(widget == bubble_->GetWidget()); DCHECK(widget == bubble_->GetWidget());
// Update the overflow button in the parent ShelfView. // Update the overflow button in the parent ShelfView.
anchor_->SchedulePaint(); overflow_button_->SchedulePaint();
bubble_ = nullptr; bubble_ = nullptr;
anchor_ = nullptr; overflow_button_ = nullptr;
shelf_view_ = nullptr; shelf_view_ = nullptr;
} }
......
...@@ -19,6 +19,7 @@ class View; ...@@ -19,6 +19,7 @@ class View;
namespace ash { namespace ash {
class OverflowBubbleView; class OverflowBubbleView;
class OverflowButton;
class ShelfView; class ShelfView;
class WmShelf; class WmShelf;
...@@ -31,16 +32,13 @@ class OverflowBubble : public views::PointerWatcher, ...@@ -31,16 +32,13 @@ class OverflowBubble : public views::PointerWatcher,
explicit OverflowBubble(WmShelf* wm_shelf); explicit OverflowBubble(WmShelf* wm_shelf);
~OverflowBubble() override; ~OverflowBubble() override;
// Shows an bubble pointing to |anchor| with |shelf_view| as its content. // Shows an bubble pointing to |overflow_button| with |shelf_view| as its
// This |shelf_view| is different than the main shelf's view and only contains // content. This |shelf_view| is different than the main shelf's view and
// the overflow items. // only contains the overflow items.
void Show(views::View* anchor, ShelfView* shelf_view); void Show(OverflowButton* overflow_button, ShelfView* shelf_view);
void Hide(); void Hide();
// Hides the bubble and schedules paint for overflow button.
void HideBubbleAndRefreshButton();
bool IsShowing() const { return !!bubble_; } bool IsShowing() const { return !!bubble_; }
ShelfView* shelf_view() { return shelf_view_; } ShelfView* shelf_view() { return shelf_view_; }
OverflowBubbleView* bubble_view() { return bubble_; } OverflowBubbleView* bubble_view() { return bubble_; }
...@@ -58,7 +56,7 @@ class OverflowBubble : public views::PointerWatcher, ...@@ -58,7 +56,7 @@ class OverflowBubble : public views::PointerWatcher,
WmShelf* wm_shelf_; WmShelf* wm_shelf_;
OverflowBubbleView* bubble_; // Owned by views hierarchy. OverflowBubbleView* bubble_; // Owned by views hierarchy.
views::View* anchor_; // Owned by ShelfView. OverflowButton* overflow_button_; // Owned by ShelfView.
// ShelfView containing the overflow items. Owned by |bubble_|. // ShelfView containing the overflow items. Owned by |bubble_|.
ShelfView* shelf_view_; ShelfView* shelf_view_;
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "ash/common/shelf/shelf_view.h" #include "ash/common/shelf/shelf_view.h"
#include "ash/common/shelf/wm_shelf.h" #include "ash/common/shelf/wm_shelf.h"
#include "ash/common/shelf/wm_shelf_util.h" #include "ash/common/shelf/wm_shelf_util.h"
#include "base/memory/ptr_util.h"
#include "grit/ash_resources.h" #include "grit/ash_resources.h"
#include "grit/ash_strings.h" #include "grit/ash_strings.h"
#include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkPaint.h"
...@@ -24,6 +25,7 @@ ...@@ -24,6 +25,7 @@
#include "ui/gfx/skia_util.h" #include "ui/gfx/skia_util.h"
#include "ui/gfx/transform.h" #include "ui/gfx/transform.h"
#include "ui/gfx/vector_icons_public.h" #include "ui/gfx/vector_icons_public.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
namespace ash { namespace ash {
...@@ -35,6 +37,10 @@ OverflowButton::OverflowButton(ShelfView* shelf_view, WmShelf* wm_shelf) ...@@ -35,6 +37,10 @@ OverflowButton::OverflowButton(ShelfView* shelf_view, WmShelf* wm_shelf)
background_alpha_(0) { background_alpha_(0) {
DCHECK(shelf_view_); DCHECK(shelf_view_);
if (MaterialDesignController::IsShelfMaterial()) { if (MaterialDesignController::IsShelfMaterial()) {
SetInkDropMode(InkDropMode::ON);
set_ink_drop_base_color(kShelfInkDropBaseColor);
set_ink_drop_visible_opacity(kShelfInkDropVisibleOpacity);
set_hide_ink_drop_when_showing_context_menu(false);
bottom_image_md_ = bottom_image_md_ =
CreateVectorIcon(gfx::VectorIconId::SHELF_OVERFLOW, kShelfIconColor); CreateVectorIcon(gfx::VectorIconId::SHELF_OVERFLOW, kShelfIconColor);
bottom_image_ = &bottom_image_md_; bottom_image_ = &bottom_image_md_;
...@@ -53,6 +59,18 @@ void OverflowButton::OnShelfAlignmentChanged() { ...@@ -53,6 +59,18 @@ void OverflowButton::OnShelfAlignmentChanged() {
SchedulePaint(); SchedulePaint();
} }
void OverflowButton::OnOverflowBubbleShown() {
AnimateInkDrop(views::InkDropState::ACTIVATED, nullptr);
if (!ash::MaterialDesignController::IsShelfMaterial())
SchedulePaint();
}
void OverflowButton::OnOverflowBubbleHidden() {
AnimateInkDrop(views::InkDropState::DEACTIVATED, nullptr);
if (!ash::MaterialDesignController::IsShelfMaterial())
SchedulePaint();
}
void OverflowButton::SetBackgroundAlpha(int alpha) { void OverflowButton::SetBackgroundAlpha(int alpha) {
background_alpha_ = alpha; background_alpha_ = alpha;
SchedulePaint(); SchedulePaint();
...@@ -64,6 +82,24 @@ void OverflowButton::OnPaint(gfx::Canvas* canvas) { ...@@ -64,6 +82,24 @@ void OverflowButton::OnPaint(gfx::Canvas* canvas) {
PaintForeground(canvas, bounds); PaintForeground(canvas, bounds);
} }
std::unique_ptr<views::InkDropRipple> OverflowButton::CreateInkDropRipple()
const {
return base::MakeUnique<views::FloodFillInkDropRipple>(
CalculateButtonBounds(), GetInkDropCenterBasedOnLastEvent(),
GetInkDropBaseColor(), ink_drop_visible_opacity());
}
bool OverflowButton::ShouldEnterPushedState(const ui::Event& event) {
if (shelf_view_->IsShowingOverflowBubble())
return false;
return CustomButton::ShouldEnterPushedState(event);
}
bool OverflowButton::ShouldShowInkDropHighlight() const {
return false;
}
void OverflowButton::NotifyClick(const ui::Event& event) { void OverflowButton::NotifyClick(const ui::Event& event) {
CustomButton::NotifyClick(event); CustomButton::NotifyClick(event);
shelf_view_->ButtonPressed(this, event, ink_drop()); shelf_view_->ButtonPressed(this, event, ink_drop());
...@@ -77,14 +113,6 @@ void OverflowButton::PaintBackground(gfx::Canvas* canvas, ...@@ -77,14 +113,6 @@ void OverflowButton::PaintBackground(gfx::Canvas* canvas,
background_paint.setColor(SkColorSetA(kShelfBaseColor, background_alpha_)); background_paint.setColor(SkColorSetA(kShelfBaseColor, background_alpha_));
canvas->DrawRoundRect(bounds, kOverflowButtonCornerRadius, canvas->DrawRoundRect(bounds, kOverflowButtonCornerRadius,
background_paint); background_paint);
if (shelf_view_->IsShowingOverflowBubble()) {
SkPaint highlight_paint;
highlight_paint.setFlags(SkPaint::kAntiAlias_Flag);
highlight_paint.setColor(kShelfButtonActivatedHighlightColor);
canvas->DrawRoundRect(bounds, kOverflowButtonCornerRadius,
highlight_paint);
}
} else { } else {
ResourceBundle& rb = ResourceBundle::GetSharedInstance(); ResourceBundle& rb = ResourceBundle::GetSharedInstance();
const gfx::ImageSkia* background = const gfx::ImageSkia* background =
...@@ -122,7 +150,7 @@ void OverflowButton::PaintForeground(gfx::Canvas* canvas, ...@@ -122,7 +150,7 @@ void OverflowButton::PaintForeground(gfx::Canvas* canvas,
bounds.y() + ((bounds.height() - image->height()) / 2)); bounds.y() + ((bounds.height() - image->height()) / 2));
} }
int OverflowButton::NonMaterialBackgroundImageId() { int OverflowButton::NonMaterialBackgroundImageId() const {
if (shelf_view_->IsShowingOverflowBubble()) if (shelf_view_->IsShowingOverflowBubble())
return IDR_AURA_NOTIFICATION_BACKGROUND_PRESSED; return IDR_AURA_NOTIFICATION_BACKGROUND_PRESSED;
else if (wm_shelf_->IsDimmed()) else if (wm_shelf_->IsDimmed())
...@@ -130,7 +158,7 @@ int OverflowButton::NonMaterialBackgroundImageId() { ...@@ -130,7 +158,7 @@ int OverflowButton::NonMaterialBackgroundImageId() {
return IDR_AURA_NOTIFICATION_BACKGROUND_NORMAL; return IDR_AURA_NOTIFICATION_BACKGROUND_NORMAL;
} }
gfx::Rect OverflowButton::CalculateButtonBounds() { gfx::Rect OverflowButton::CalculateButtonBounds() const {
ShelfAlignment alignment = wm_shelf_->GetAlignment(); ShelfAlignment alignment = wm_shelf_->GetAlignment();
gfx::Rect bounds(GetContentsBounds()); gfx::Rect bounds(GetContentsBounds());
ResourceBundle& rb = ResourceBundle::GetSharedInstance(); ResourceBundle& rb = ResourceBundle::GetSharedInstance();
......
...@@ -21,15 +21,18 @@ class OverflowButton : public views::CustomButton { ...@@ -21,15 +21,18 @@ class OverflowButton : public views::CustomButton {
~OverflowButton() override; ~OverflowButton() override;
void OnShelfAlignmentChanged(); void OnShelfAlignmentChanged();
void OnOverflowBubbleShown();
void OnOverflowBubbleHidden();
// Sets alpha value of the background and schedules a paint. // Sets alpha value of the background and schedules a paint.
void SetBackgroundAlpha(int alpha); void SetBackgroundAlpha(int alpha);
private: private:
// views::View:
void OnPaint(gfx::Canvas* canvas) override;
// views::CustomButton: // views::CustomButton:
void OnPaint(gfx::Canvas* canvas) override;
std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
bool ShouldEnterPushedState(const ui::Event& event) override;
bool ShouldShowInkDropHighlight() const override;
void NotifyClick(const ui::Event& event) override; void NotifyClick(const ui::Event& event) override;
// Helper functions to paint the background and foreground of the button // Helper functions to paint the background and foreground of the button
...@@ -41,10 +44,10 @@ class OverflowButton : public views::CustomButton { ...@@ -41,10 +44,10 @@ class OverflowButton : public views::CustomButton {
// current shelf state. // current shelf state.
// TODO(tdanderson): Remove this once the material design shelf is enabled // TODO(tdanderson): Remove this once the material design shelf is enabled
// by default. See crbug.com/614453. // by default. See crbug.com/614453.
int NonMaterialBackgroundImageId(); int NonMaterialBackgroundImageId() const;
// Calculates the bounds of the overflow button based on the shelf alignment. // Calculates the bounds of the overflow button based on the shelf alignment.
gfx::Rect CalculateButtonBounds(); gfx::Rect CalculateButtonBounds() const;
// Used for bottom shelf alignment. |bottom_image_| points to // Used for bottom shelf alignment. |bottom_image_| points to
// |bottom_image_md_| for material design, otherwise it is points to a // |bottom_image_md_| for material design, otherwise it is points to a
......
...@@ -1854,7 +1854,7 @@ void ShelfView::OnMenuClosed(views::InkDrop* ink_drop) { ...@@ -1854,7 +1854,7 @@ void ShelfView::OnMenuClosed(views::InkDrop* ink_drop) {
// Hide the hide overflow bubble after showing a context menu for its items. // Hide the hide overflow bubble after showing a context menu for its items.
if (owner_overflow_bubble_) if (owner_overflow_bubble_)
owner_overflow_bubble_->HideBubbleAndRefreshButton(); owner_overflow_bubble_->Hide();
closing_event_time_ = launcher_menu_runner_->closing_event_time(); closing_event_time_ = launcher_menu_runner_->closing_event_time();
......
This diff is collapsed.
...@@ -68,10 +68,19 @@ bool ShelfViewTestAPI::IsOverflowButtonVisible() { ...@@ -68,10 +68,19 @@ bool ShelfViewTestAPI::IsOverflowButtonVisible() {
} }
void ShelfViewTestAPI::ShowOverflowBubble() { void ShelfViewTestAPI::ShowOverflowBubble() {
if (!shelf_view_->IsShowingOverflowBubble()) DCHECK(!shelf_view_->IsShowingOverflowBubble());
shelf_view_->ToggleOverflowBubble(); shelf_view_->ToggleOverflowBubble();
} }
void ShelfViewTestAPI::HideOverflowBubble() {
DCHECK(shelf_view_->IsShowingOverflowBubble());
shelf_view_->ToggleOverflowBubble();
}
bool ShelfViewTestAPI::IsShowingOverflowBubble() const {
return shelf_view_->IsShowingOverflowBubble();
}
const gfx::Rect& ShelfViewTestAPI::GetBoundsByIndex(int index) { const gfx::Rect& ShelfViewTestAPI::GetBoundsByIndex(int index) {
return shelf_view_->view_model_->view_at(index)->bounds(); return shelf_view_->view_model_->view_at(index)->bounds();
} }
...@@ -110,6 +119,10 @@ OverflowBubble* ShelfViewTestAPI::overflow_bubble() { ...@@ -110,6 +119,10 @@ OverflowBubble* ShelfViewTestAPI::overflow_bubble() {
return shelf_view_->overflow_bubble_.get(); return shelf_view_->overflow_bubble_.get();
} }
OverflowButton* ShelfViewTestAPI::overflow_button() const {
return shelf_view_->overflow_button_;
}
ShelfTooltipManager* ShelfViewTestAPI::tooltip_manager() { ShelfTooltipManager* ShelfViewTestAPI::tooltip_manager() {
return &shelf_view_->tooltip_; return &shelf_view_->tooltip_;
} }
......
...@@ -25,6 +25,7 @@ class InkDrop; ...@@ -25,6 +25,7 @@ class InkDrop;
namespace ash { namespace ash {
class OverflowBubble; class OverflowBubble;
class OverflowButton;
class ShelfButton; class ShelfButton;
class ShelfButtonPressedMetricTracker; class ShelfButtonPressedMetricTracker;
class ShelfDelegate; class ShelfDelegate;
...@@ -61,6 +62,12 @@ class ShelfViewTestAPI { ...@@ -61,6 +62,12 @@ class ShelfViewTestAPI {
// Makes shelf view show its overflow bubble. // Makes shelf view show its overflow bubble.
void ShowOverflowBubble(); void ShowOverflowBubble();
// Makes shelf view hide its overflow bubble.
void HideOverflowBubble();
// Returns true if the overflow bubble is visible.
bool IsShowingOverflowBubble() const;
// Sets animation duration in milliseconds for test. // Sets animation duration in milliseconds for test.
void SetAnimationDuration(int duration_ms); void SetAnimationDuration(int duration_ms);
...@@ -79,6 +86,9 @@ class ShelfViewTestAPI { ...@@ -79,6 +86,9 @@ class ShelfViewTestAPI {
// An accessor for overflow bubble. // An accessor for overflow bubble.
OverflowBubble* overflow_bubble(); OverflowBubble* overflow_bubble();
// An accessor for overflow button.
OverflowButton* overflow_button() const;
// Returns the preferred size of |shelf_view_|. // Returns the preferred size of |shelf_view_|.
gfx::Size GetPreferredSize(); gfx::Size GetPreferredSize();
......
...@@ -58,6 +58,8 @@ class InkDropHostView::InkDropGestureHandler : public ui::EventHandler { ...@@ -58,6 +58,8 @@ class InkDropHostView::InkDropGestureHandler : public ui::EventHandler {
InkDropState ink_drop_state = InkDropState::HIDDEN; InkDropState ink_drop_state = InkDropState::HIDDEN;
switch (event->type()) { switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN: case ui::ET_GESTURE_TAP_DOWN:
if (current_ink_drop_state == InkDropState::ACTIVATED)
return;
ink_drop_state = InkDropState::ACTION_PENDING; ink_drop_state = InkDropState::ACTION_PENDING;
// The ui::ET_GESTURE_TAP_DOWN event needs to be marked as handled so // The ui::ET_GESTURE_TAP_DOWN event needs to be marked as handled so
// that // that
...@@ -65,16 +67,17 @@ class InkDropHostView::InkDropGestureHandler : public ui::EventHandler { ...@@ -65,16 +67,17 @@ class InkDropHostView::InkDropGestureHandler : public ui::EventHandler {
event->SetHandled(); event->SetHandled();
break; break;
case ui::ET_GESTURE_LONG_PRESS: case ui::ET_GESTURE_LONG_PRESS:
if (current_ink_drop_state == InkDropState::ACTIVATED)
return;
ink_drop_state = InkDropState::ALTERNATE_ACTION_PENDING; ink_drop_state = InkDropState::ALTERNATE_ACTION_PENDING;
break; break;
case ui::ET_GESTURE_LONG_TAP: case ui::ET_GESTURE_LONG_TAP:
ink_drop_state = InkDropState::ALTERNATE_ACTION_TRIGGERED; ink_drop_state = InkDropState::ALTERNATE_ACTION_TRIGGERED;
break; break;
case ui::ET_GESTURE_END: case ui::ET_GESTURE_END:
case ui::ET_GESTURE_SCROLL_BEGIN:
if (current_ink_drop_state == InkDropState::ACTIVATED) if (current_ink_drop_state == InkDropState::ACTIVATED)
return; return;
// Fall through to ui::ET_GESTURE_SCROLL_BEGIN case.
case ui::ET_GESTURE_SCROLL_BEGIN:
ink_drop_state = InkDropState::HIDDEN; ink_drop_state = InkDropState::HIDDEN;
break; break;
default: default:
......
...@@ -470,8 +470,13 @@ void CustomButton::NotifyClick(const ui::Event& event) { ...@@ -470,8 +470,13 @@ void CustomButton::NotifyClick(const ui::Event& event) {
} }
void CustomButton::OnClickCanceled(const ui::Event& event) { void CustomButton::OnClickCanceled(const ui::Event& event) {
if (ink_drop()->GetTargetInkDropState() ==
views::InkDropState::ACTION_PENDING ||
ink_drop()->GetTargetInkDropState() ==
views::InkDropState::ALTERNATE_ACTION_PENDING) {
AnimateInkDrop(views::InkDropState::HIDDEN, AnimateInkDrop(views::InkDropState::HIDDEN,
ui::LocatedEvent::FromIfValid(&event)); ui::LocatedEvent::FromIfValid(&event));
}
Button::OnClickCanceled(event); Button::OnClickCanceled(event);
} }
......
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