1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/message_center/views/notification_view_md.h"
6
7 #include <stddef.h>
8 #include <memory>
9
10 #include "base/i18n/case_conversion.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "base/strings/string_util.h"
13 #include "components/url_formatter/elide_url.h"
14 #include "ui/base/class_property.h"
15 #include "ui/base/cursor/cursor.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/events/base_event_utils.h"
18 #include "ui/events/gesture_detection/gesture_provider_config_helper.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/color_palette.h"
21 #include "ui/gfx/geometry/rect.h"
22 #include "ui/gfx/geometry/size.h"
23 #include "ui/gfx/image/image_skia_operations.h"
24 #include "ui/gfx/paint_vector_icon.h"
25 #include "ui/gfx/skia_util.h"
26 #include "ui/gfx/text_constants.h"
27 #include "ui/gfx/text_elider.h"
28 #include "ui/message_center/message_center.h"
29 #include "ui/message_center/public/cpp/message_center_constants.h"
30 #include "ui/message_center/public/cpp/notification.h"
31 #include "ui/message_center/public/cpp/notification_types.h"
32 #include "ui/message_center/vector_icons.h"
33 #include "ui/message_center/views/notification_background_painter.h"
34 #include "ui/message_center/views/notification_control_buttons_view.h"
35 #include "ui/message_center/views/notification_header_view.h"
36 #include "ui/message_center/views/padded_button.h"
37 #include "ui/message_center/views/proportional_image_view.h"
38 #include "ui/strings/grit/ui_strings.h"
39 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
40 #include "ui/views/animation/ink_drop_highlight.h"
41 #include "ui/views/animation/ink_drop_impl.h"
42 #include "ui/views/animation/ink_drop_mask.h"
43 #include "ui/views/background.h"
44 #include "ui/views/border.h"
45 #include "ui/views/controls/button/image_button.h"
46 #include "ui/views/controls/button/radio_button.h"
47 #include "ui/views/controls/highlight_path_generator.h"
48 #include "ui/views/controls/image_view.h"
49 #include "ui/views/controls/label.h"
50 #include "ui/views/controls/progress_bar.h"
51 #include "ui/views/controls/textfield/textfield.h"
52 #include "ui/views/focus/focus_manager.h"
53 #include "ui/views/layout/box_layout.h"
54 #include "ui/views/layout/fill_layout.h"
55 #include "ui/views/native_cursor.h"
56 #include "ui/views/widget/widget.h"
57 #include "ui/views/widget/widget_delegate.h"
58
59 namespace message_center {
60
61 namespace {
62
63 // Dimensions.
64 constexpr gfx::Insets kContentRowPadding(0, 12, 16, 12);
65 constexpr gfx::Insets kActionsRowPadding(8, 8, 8, 8);
66 constexpr int kActionsRowHorizontalSpacing = 8;
67 constexpr gfx::Insets kActionButtonPadding(0, 12, 0, 12);
68 constexpr gfx::Insets kStatusTextPadding(4, 0, 0, 0);
69 constexpr gfx::Size kActionButtonMinSize(0, 32);
70 // TODO(tetsui): Move |kIconViewSize| to public/cpp/message_center_constants.h
71 // and merge with contradicting |kNotificationIconSize|.
72 constexpr gfx::Size kIconViewSize(36, 36);
73 constexpr gfx::Insets kLargeImageContainerPadding(0, 16, 16, 16);
74 constexpr gfx::Size kLargeImageMinSize(328, 0);
75 constexpr gfx::Size kLargeImageMaxSize(328, 218);
76 constexpr gfx::Insets kLeftContentPadding(2, 4, 0, 4);
77 constexpr gfx::Insets kLeftContentPaddingWithIcon(2, 4, 0, 12);
78 constexpr gfx::Insets kInputTextfieldPadding(16, 16, 16, 0);
79 constexpr gfx::Insets kInputReplyButtonPadding(0, 14, 0, 14);
80 constexpr gfx::Insets kSettingsRowPadding(8, 0, 0, 0);
81 constexpr gfx::Insets kSettingsRadioButtonPadding(14, 18, 14, 18);
82 constexpr gfx::Insets kSettingsButtonRowPadding(8);
83
84 // Background of inline actions area.
85 constexpr SkColor kActionsRowBackgroundColor = SkColorSetRGB(0xee, 0xee, 0xee);
86 // Ripple ink drop opacity of action buttons.
87 const float kActionButtonInkDropRippleVisibleOpacity = 0.08f;
88 // Highlight (hover) ink drop opacity of action buttons.
89 const float kActionButtonInkDropHighlightVisibleOpacity = 0.08f;
90 // Text color of action button.
91 constexpr SkColor kActionButtonTextColor = gfx::kGoogleBlue600;
92 // Background color of the large image.
93 constexpr SkColor kLargeImageBackgroundColor = SkColorSetRGB(0xf5, 0xf5, 0xf5);
94 // Background color of the inline settings.
95 constexpr SkColor kInlineSettingsBackgroundColor =
96 SkColorSetRGB(0xEE, 0xEE, 0xEE);
97
98 // Text color and icon color of inline reply area when the textfield is empty.
99 constexpr SkColor kTextfieldPlaceholderTextColorMD =
100 SkColorSetA(SK_ColorWHITE, 0x8A);
101 constexpr SkColor kTextfieldPlaceholderIconColorMD =
102 SkColorSetA(SK_ColorWHITE, 0x60);
103
104 // The icon size of inline reply input field.
105 constexpr int kInputReplyButtonSize = 20;
106
107 // Max number of lines for title_view_.
108 constexpr int kMaxLinesForTitleView = 1;
109 // Max number of lines for message_view_.
110 constexpr int kMaxLinesForMessageView = 1;
111 constexpr int kMaxLinesForExpandedMessageView = 4;
112
113 constexpr int kCompactTitleMessageViewSpacing = 12;
114
115 constexpr int kProgressBarHeight = 4;
116
117 constexpr int kMessageViewWidthWithIcon =
118 kNotificationWidth - kIconViewSize.width() -
119 kLeftContentPaddingWithIcon.left() - kLeftContentPaddingWithIcon.right() -
120 kContentRowPadding.left() - kContentRowPadding.right();
121
122 constexpr int kMessageViewWidth =
123 kNotificationWidth - kLeftContentPadding.left() -
124 kLeftContentPadding.right() - kContentRowPadding.left() -
125 kContentRowPadding.right();
126
127 const int kMinPixelsPerTitleCharacterMD = 4;
128
129 // Character limit = pixels per line * line limit / min. pixels per character.
130 constexpr size_t kMessageCharacterLimitMD =
131 kNotificationWidth * kMessageExpandedLineLimit / 3;
132
133 // The default is 12, so this normally come out to 13.
134 constexpr int kTextFontSizeDelta = 1;
135
136 // In progress notification, if both the title and the message are long, the
137 // message would be prioritized and the title would be elided.
138 // However, it is not perferable that we completely omit the title, so
139 // the ratio of the message width is limited to this value.
140 constexpr double kProgressNotificationMessageRatio = 0.7;
141
142 // Line height of title and message views.
143 constexpr int kLineHeightMD = 17;
144
145 // This key/property allows tagging the textfield with its index.
146 DEFINE_UI_CLASS_PROPERTY_KEY(int, kTextfieldIndexKey, 0U)
147
148 // FontList for the texts except for the header.
GetTextFontList()149 gfx::FontList GetTextFontList() {
150 gfx::Font default_font;
151 gfx::Font font = default_font.Derive(kTextFontSizeDelta, gfx::Font::NORMAL,
152 gfx::Font::Weight::NORMAL);
153 return gfx::FontList(font);
154 }
155
156 class ClickActivator : public ui::EventHandler {
157 public:
ClickActivator(NotificationViewMD * owner)158 explicit ClickActivator(NotificationViewMD* owner) : owner_(owner) {}
159 ~ClickActivator() override = default;
160
161 private:
162 // ui::EventHandler
OnEvent(ui::Event * event)163 void OnEvent(ui::Event* event) override {
164 if (event->type() == ui::ET_MOUSE_PRESSED ||
165 event->type() == ui::ET_GESTURE_TAP) {
166 owner_->Activate();
167 }
168 }
169
170 NotificationViewMD* const owner_;
171
172 DISALLOW_COPY_AND_ASSIGN(ClickActivator);
173 };
174
175 // Creates a view responsible for drawing each list notification item's title
176 // and message next to each other within a single column.
CreateItemView(const NotificationItem & item)177 std::unique_ptr<views::View> CreateItemView(const NotificationItem& item) {
178 auto view = std::make_unique<views::View>();
179 view->SetLayoutManager(std::make_unique<views::BoxLayout>(
180 views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0));
181
182 const gfx::FontList font_list = GetTextFontList();
183
184 auto* title = new views::Label(item.title);
185 title->SetFontList(font_list);
186 title->SetCollapseWhenHidden(true);
187 title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
188 title->SetEnabledColor(kRegularTextColorMD);
189 title->SetBackgroundColor(kNotificationBackgroundColor);
190 title->SetAutoColorReadabilityEnabled(false);
191 view->AddChildView(title);
192
193 views::Label* message = new views::Label(l10n_util::GetStringFUTF16(
194 IDS_MESSAGE_CENTER_LIST_NOTIFICATION_MESSAGE_WITH_DIVIDER, item.message));
195 message->SetFontList(font_list);
196 message->SetCollapseWhenHidden(true);
197 message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
198 message->SetEnabledColor(kDimTextColorMD);
199 message->SetBackgroundColor(kNotificationBackgroundColor);
200 message->SetAutoColorReadabilityEnabled(false);
201 view->AddChildView(message);
202 return view;
203 }
204
205 } // anonymous namespace
206
207 // CompactTitleMessageView /////////////////////////////////////////////////////
208
209 CompactTitleMessageView::~CompactTitleMessageView() = default;
210
GetClassName() const211 const char* CompactTitleMessageView::GetClassName() const {
212 return "CompactTitleMessageView";
213 }
214
CompactTitleMessageView()215 CompactTitleMessageView::CompactTitleMessageView() {
216 const gfx::FontList& font_list = GetTextFontList();
217
218 title_ = new views::Label();
219 title_->SetFontList(font_list);
220 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
221 title_->SetEnabledColor(kRegularTextColorMD);
222 title_->SetBackgroundColor(kNotificationBackgroundColor);
223 AddChildView(title_);
224
225 message_ = new views::Label();
226 message_->SetFontList(font_list);
227 message_->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
228 message_->SetEnabledColor(kDimTextColorMD);
229 message_->SetBackgroundColor(kNotificationBackgroundColor);
230 AddChildView(message_);
231 }
232
CalculatePreferredSize() const233 gfx::Size CompactTitleMessageView::CalculatePreferredSize() const {
234 gfx::Size title_size = title_->GetPreferredSize();
235 gfx::Size message_size = message_->GetPreferredSize();
236 return gfx::Size(title_size.width() + message_size.width() +
237 kCompactTitleMessageViewSpacing,
238 std::max(title_size.height(), message_size.height()));
239 }
240
Layout()241 void CompactTitleMessageView::Layout() {
242 // Elides title and message.
243 // * If the message is too long, the message occupies at most
244 // kProgressNotificationMessageRatio of the width.
245 // * If the title is too long, the full content of the message is shown,
246 // kCompactTitleMessageViewSpacing is added between them, and the elided
247 // title is shown.
248 // * If they are short enough, the title is left-aligned and the message is
249 // right-aligned.
250 const int message_width = std::min(
251 message_->GetPreferredSize().width(),
252 title_->GetPreferredSize().width() > 0
253 ? static_cast<int>(kProgressNotificationMessageRatio * width())
254 : width());
255 const int title_width =
256 std::max(0, width() - message_width - kCompactTitleMessageViewSpacing);
257
258 title_->SetBounds(0, 0, title_width, height());
259 message_->SetBounds(width() - message_width, 0, message_width, height());
260 }
261
set_title(const base::string16 & title)262 void CompactTitleMessageView::set_title(const base::string16& title) {
263 title_->SetText(title);
264 }
265
set_message(const base::string16 & message)266 void CompactTitleMessageView::set_message(const base::string16& message) {
267 message_->SetText(message);
268 }
269
270 // LargeImageView //////////////////////////////////////////////////////////////
271
LargeImageView()272 LargeImageView::LargeImageView() {
273 SetBackground(views::CreateSolidBackground(kLargeImageBackgroundColor));
274 }
275
276 LargeImageView::~LargeImageView() = default;
277
SetImage(const gfx::ImageSkia & image)278 void LargeImageView::SetImage(const gfx::ImageSkia& image) {
279 image_ = image;
280 gfx::Size preferred_size = GetResizedImageSize();
281 preferred_size.SetToMax(kLargeImageMinSize);
282 preferred_size.SetToMin(kLargeImageMaxSize);
283 SetPreferredSize(preferred_size);
284 SchedulePaint();
285 Layout();
286 }
287
OnPaint(gfx::Canvas * canvas)288 void LargeImageView::OnPaint(gfx::Canvas* canvas) {
289 views::View::OnPaint(canvas);
290
291 gfx::Size resized_size = GetResizedImageSize();
292 gfx::Size drawn_size = resized_size;
293 drawn_size.SetToMin(kLargeImageMaxSize);
294 gfx::Rect drawn_bounds = GetContentsBounds();
295 drawn_bounds.ClampToCenteredSize(drawn_size);
296
297 gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage(
298 image_, skia::ImageOperations::RESIZE_BEST, resized_size);
299
300 // Cut off the overflown part.
301 gfx::ImageSkia drawn_image = gfx::ImageSkiaOperations::ExtractSubset(
302 resized_image, gfx::Rect(drawn_size));
303
304 canvas->DrawImageInt(drawn_image, drawn_bounds.x(), drawn_bounds.y());
305 }
306
GetClassName() const307 const char* LargeImageView::GetClassName() const {
308 return "LargeImageView";
309 }
310
311 // Returns expected size of the image right after resizing.
312 // The GetResizedImageSize().width() <= kLargeImageMaxSize.width() holds, but
313 // GetResizedImageSize().height() may be larger than kLargeImageMaxSize.height()
314 // In this case, the overflown part will be just cutted off from the view.
GetResizedImageSize()315 gfx::Size LargeImageView::GetResizedImageSize() {
316 gfx::Size original_size = image_.size();
317 if (original_size.width() <= kLargeImageMaxSize.width())
318 return image_.size();
319
320 const double proportion =
321 original_size.height() / static_cast<double>(original_size.width());
322 gfx::Size resized_size;
323 resized_size.SetSize(kLargeImageMaxSize.width(),
324 kLargeImageMaxSize.width() * proportion);
325 return resized_size;
326 }
327
328 // NotificationButtonMD ////////////////////////////////////////////////////////
329
NotificationButtonMD(views::ButtonListener * listener,const base::string16 & label,const base::Optional<base::string16> & placeholder)330 NotificationButtonMD::NotificationButtonMD(
331 views::ButtonListener* listener,
332 const base::string16& label,
333 const base::Optional<base::string16>& placeholder)
334 : views::LabelButton(listener,
335 base::i18n::ToUpper(label),
336 views::style::CONTEXT_BUTTON_MD),
337 placeholder_(placeholder) {
338 SetHorizontalAlignment(gfx::ALIGN_CENTER);
339 SetInkDropMode(InkDropMode::ON);
340 set_has_ink_drop_action_on_click(true);
341 set_ink_drop_base_color(SK_ColorBLACK);
342 set_ink_drop_visible_opacity(kActionButtonInkDropRippleVisibleOpacity);
343 SetEnabledTextColors(kActionButtonTextColor);
344 SetBorder(views::CreateEmptyBorder(kActionButtonPadding));
345 SetMinSize(kActionButtonMinSize);
346 SetFocusForPlatform();
347
348 views::InstallRectHighlightPathGenerator(this);
349 }
350
351 NotificationButtonMD::~NotificationButtonMD() = default;
352
SetText(const base::string16 & text)353 void NotificationButtonMD::SetText(const base::string16& text) {
354 views::LabelButton::SetText(base::i18n::ToUpper(text));
355 }
356
GetClassName() const357 const char* NotificationButtonMD::GetClassName() const {
358 return "NotificationButtonMD";
359 }
360
361 std::unique_ptr<views::InkDropHighlight>
CreateInkDropHighlight() const362 NotificationButtonMD::CreateInkDropHighlight() const {
363 std::unique_ptr<views::InkDropHighlight> highlight =
364 views::LabelButton::CreateInkDropHighlight();
365 highlight->set_visible_opacity(kActionButtonInkDropHighlightVisibleOpacity);
366 return highlight;
367 }
368
369 // NotificationInputContainerMD ////////////////////////////////////////////////
370
NotificationInputContainerMD(NotificationInputDelegate * delegate)371 NotificationInputContainerMD::NotificationInputContainerMD(
372 NotificationInputDelegate* delegate)
373 : delegate_(delegate),
374 ink_drop_container_(new views::InkDropContainerView()),
375 textfield_(new views::Textfield()),
376 button_(new views::ImageButton(this)) {
377 auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
378 views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 0));
379 SetBackground(views::CreateSolidBackground(kActionsRowBackgroundColor));
380
381 SetInkDropMode(InkDropMode::ON);
382 set_ink_drop_visible_opacity(1);
383
384 AddChildView(ink_drop_container_);
385
386 textfield_->set_controller(this);
387 textfield_->SetTextColor(SK_ColorWHITE);
388 textfield_->SetBackgroundColor(SK_ColorTRANSPARENT);
389 textfield_->set_placeholder_text_color(kTextfieldPlaceholderTextColorMD);
390 textfield_->SetBorder(views::CreateEmptyBorder(kInputTextfieldPadding));
391 AddChildView(textfield_);
392 layout->SetFlexForView(textfield_, 1);
393
394 button_->SetBorder(views::CreateEmptyBorder(kInputReplyButtonPadding));
395 button_->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
396 button_->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
397 OnAfterUserAction(textfield_);
398 AddChildView(button_);
399
400 views::InstallRectHighlightPathGenerator(this);
401 }
402
403 NotificationInputContainerMD::~NotificationInputContainerMD() = default;
404
AnimateBackground(const ui::Event & event)405 void NotificationInputContainerMD::AnimateBackground(const ui::Event& event) {
406 // Try to get a located event. This can be NULL if triggered via keyboard.
407 const ui::LocatedEvent* located_event = ui::LocatedEvent::FromIfValid(&event);
408 // Use default animation if location is out of bounds.
409 if (located_event && !View::HitTestPoint(located_event->location()))
410 located_event = nullptr;
411 AnimateInkDrop(views::InkDropState::ACTION_PENDING, located_event);
412 }
413
AddLayerBeneathView(ui::Layer * layer)414 void NotificationInputContainerMD::AddLayerBeneathView(ui::Layer* layer) {
415 // When a ink drop layer is added it is stacked between the textfield/button
416 // and the parent (|this|). Since the ink drop is opaque, we have to paint the
417 // textfield/button on their own layers in otherwise they remain painted on
418 // |this|'s layer which would be covered by the ink drop.
419 textfield_->SetPaintToLayer();
420 textfield_->layer()->SetFillsBoundsOpaquely(false);
421 button_->SetPaintToLayer();
422 button_->layer()->SetFillsBoundsOpaquely(false);
423 ink_drop_container_->AddLayerBeneathView(layer);
424 }
425
RemoveLayerBeneathView(ui::Layer * layer)426 void NotificationInputContainerMD::RemoveLayerBeneathView(ui::Layer* layer) {
427 ink_drop_container_->RemoveLayerBeneathView(layer);
428 textfield_->DestroyLayer();
429 button_->DestroyLayer();
430 }
431
432 std::unique_ptr<views::InkDropRipple>
CreateInkDropRipple() const433 NotificationInputContainerMD::CreateInkDropRipple() const {
434 return std::make_unique<views::FloodFillInkDropRipple>(
435 size(), GetInkDropCenterBasedOnLastEvent(), GetInkDropBaseColor(),
436 ink_drop_visible_opacity());
437 }
438
GetInkDropBaseColor() const439 SkColor NotificationInputContainerMD::GetInkDropBaseColor() const {
440 return gfx::kGoogleBlue600;
441 }
442
HandleKeyEvent(views::Textfield * sender,const ui::KeyEvent & event)443 bool NotificationInputContainerMD::HandleKeyEvent(views::Textfield* sender,
444 const ui::KeyEvent& event) {
445 if (event.type() == ui::ET_KEY_PRESSED &&
446 event.key_code() == ui::VKEY_RETURN) {
447 delegate_->OnNotificationInputSubmit(
448 textfield_->GetProperty(kTextfieldIndexKey), textfield_->GetText());
449 textfield_->SetText(base::string16());
450 return true;
451 }
452 return event.type() == ui::ET_KEY_RELEASED;
453 }
454
OnAfterUserAction(views::Textfield * sender)455 void NotificationInputContainerMD::OnAfterUserAction(views::Textfield* sender) {
456 DCHECK_EQ(sender, textfield_);
457 button_->SetImage(
458 views::Button::STATE_NORMAL,
459 gfx::CreateVectorIcon(kNotificationInlineReplyIcon, kInputReplyButtonSize,
460 textfield_->GetText().empty()
461 ? kTextfieldPlaceholderIconColorMD
462 : SK_ColorWHITE));
463 }
464
ButtonPressed(views::Button * sender,const ui::Event & event)465 void NotificationInputContainerMD::ButtonPressed(views::Button* sender,
466 const ui::Event& event) {
467 if (sender == button_) {
468 delegate_->OnNotificationInputSubmit(
469 textfield_->GetProperty(kTextfieldIndexKey), textfield_->GetText());
470 }
471 }
472
473 // InlineSettingsRadioButton ///////////////////////////////////////////////////
474
475 class InlineSettingsRadioButton : public views::RadioButton {
476 public:
InlineSettingsRadioButton(const base::string16 & label_text)477 explicit InlineSettingsRadioButton(const base::string16& label_text)
478 : views::RadioButton(label_text, 1 /* group */) {
479 SetEnabledTextColors(kRegularTextColorMD);
480 label()->SetFontList(GetTextFontList());
481 label()->SetBackgroundColor(kInlineSettingsBackgroundColor);
482 label()->SetSubpixelRenderingEnabled(false);
483 }
484
485 private:
486 // views::RadioButton:
GetIconImageColor(int icon_state) const487 SkColor GetIconImageColor(int icon_state) const override {
488 return (icon_state & IconState::CHECKED) ? kActionButtonTextColor
489 : kRegularTextColorMD;
490 }
491 };
492
493 // NotificationInkDropImpl /////////////////////////////////////////////////////
494
495 class NotificationInkDropImpl : public views::InkDropImpl {
496 public:
NotificationInkDropImpl(views::InkDropHostView * ink_drop_host,const gfx::Size & host_size)497 NotificationInkDropImpl(views::InkDropHostView* ink_drop_host,
498 const gfx::Size& host_size)
499 : views::InkDropImpl(ink_drop_host, host_size) {
500 SetAutoHighlightMode(views::InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE);
501 }
502
HostSizeChanged(const gfx::Size & new_size)503 void HostSizeChanged(const gfx::Size& new_size) override {
504 // Prevent a call to InkDropImpl::HostSizeChanged which recreates the ripple
505 // and stops the currently active animation: http://crbug.com/915222.
506 }
507 };
508
509 // ////////////////////////////////////////////////////////////
510 // NotificationViewMD
511 // ////////////////////////////////////////////////////////////
512
513 class NotificationViewMD::NotificationViewMDPathGenerator
514 : public views::HighlightPathGenerator {
515 public:
516 NotificationViewMDPathGenerator() = default;
517 NotificationViewMDPathGenerator(const NotificationViewMDPathGenerator&) =
518 delete;
519 NotificationViewMDPathGenerator& operator=(
520 const NotificationViewMDPathGenerator&) = delete;
521
522 // views::HighlightPathGenerator:
GetRoundRect(const gfx::RectF & rect)523 base::Optional<RoundRect> GetRoundRect(const gfx::RectF& rect) override {
524 RoundRect round_rect;
525 round_rect.bounds = rect;
526 if (!preferred_size_.IsEmpty())
527 round_rect.bounds.set_size(gfx::SizeF(preferred_size_));
528 round_rect.corner_radius = gfx::RoundedCornersF(
529 top_radius_, top_radius_, bottom_radius_, bottom_radius_);
530 return round_rect;
531 }
532
set_top_radius(int val)533 void set_top_radius(int val) { top_radius_ = val; }
set_bottom_radius(int val)534 void set_bottom_radius(int val) { bottom_radius_ = val; }
set_preferred_size(const gfx::Size & val)535 void set_preferred_size(const gfx::Size& val) { preferred_size_ = val; }
536
537 private:
538 int top_radius_ = 0;
539 int bottom_radius_ = 0;
540
541 // This custom PathGenerator is used for the ink drop clipping bounds. By
542 // setting |preferred_size_| we set the correct clip bounds in
543 // GetRoundRect(). This is needed as the correct bounds for the ink drop are
544 // required before a Layout() on the view is run. See
545 // http://crbug.com/915222.
546 gfx::Size preferred_size_;
547 };
548
CreateOrUpdateViews(const Notification & notification)549 void NotificationViewMD::CreateOrUpdateViews(const Notification& notification) {
550 left_content_count_ = 0;
551
552 CreateOrUpdateContextTitleView(notification);
553 CreateOrUpdateTitleView(notification);
554 CreateOrUpdateMessageView(notification);
555 CreateOrUpdateCompactTitleMessageView(notification);
556 CreateOrUpdateProgressBarView(notification);
557 CreateOrUpdateProgressStatusView(notification);
558 CreateOrUpdateListItemViews(notification);
559 CreateOrUpdateIconView(notification);
560 CreateOrUpdateSmallIconView(notification);
561 CreateOrUpdateImageView(notification);
562 CreateOrUpdateInlineSettingsViews(notification);
563 UpdateViewForExpandedState(expanded_);
564 // Should be called at the last because SynthesizeMouseMoveEvent() requires
565 // everything is in the right location when called.
566 CreateOrUpdateActionButtonViews(notification);
567 }
568
NotificationViewMD(const Notification & notification)569 NotificationViewMD::NotificationViewMD(const Notification& notification)
570 : MessageView(notification),
571 ink_drop_container_(new views::InkDropContainerView()) {
572 SetLayoutManager(std::make_unique<views::BoxLayout>(
573 views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0));
574
575 set_ink_drop_visible_opacity(1);
576
577 AddChildView(ink_drop_container_);
578
579 control_buttons_view_ =
580 std::make_unique<NotificationControlButtonsView>(this);
581 control_buttons_view_->set_owned_by_client();
582
583 // |header_row_| contains app_icon, app_name, control buttons, etc...
584 header_row_ = new NotificationHeaderView(this);
585 header_row_->AddChildView(control_buttons_view_.get());
586 AddChildView(header_row_);
587
588 // |content_row_| contains title, message, image, progressbar, etc...
589 content_row_ = new views::View();
590 auto* content_row_layout =
591 content_row_->SetLayoutManager(std::make_unique<views::BoxLayout>(
592 views::BoxLayout::Orientation::kHorizontal, kContentRowPadding, 0));
593 content_row_layout->set_cross_axis_alignment(
594 views::BoxLayout::CrossAxisAlignment::kStart);
595 AddChildView(content_row_);
596
597 // |left_content_| contains most contents like title, message, etc...
598 left_content_ = new views::View();
599 left_content_->SetLayoutManager(std::make_unique<views::BoxLayout>(
600 views::BoxLayout::Orientation::kVertical, gfx::Insets(), 0));
601 left_content_->SetBorder(views::CreateEmptyBorder(kLeftContentPadding));
602 content_row_->AddChildView(left_content_);
603 content_row_layout->SetFlexForView(left_content_, 1);
604
605 // |right_content_| contains notification icon and small image.
606 right_content_ = new views::View();
607 right_content_->SetLayoutManager(std::make_unique<views::FillLayout>());
608 content_row_->AddChildView(right_content_);
609
610 // |action_row_| contains inline action buttons and inline textfield.
611 actions_row_ = new views::View();
612 actions_row_->SetVisible(false);
613 actions_row_->SetLayoutManager(std::make_unique<views::FillLayout>());
614 AddChildView(actions_row_);
615
616 // |action_buttons_row_| contains inline action buttons.
617 action_buttons_row_ = new views::View();
618 action_buttons_row_->SetLayoutManager(std::make_unique<views::BoxLayout>(
619 views::BoxLayout::Orientation::kHorizontal, kActionsRowPadding,
620 kActionsRowHorizontalSpacing));
621 action_buttons_row_->SetVisible(false);
622 actions_row_->AddChildView(action_buttons_row_);
623
624 // |inline_reply_| is a container for an inline textfield.
625 inline_reply_ = new NotificationInputContainerMD(this);
626 inline_reply_->SetVisible(false);
627 actions_row_->AddChildView(inline_reply_);
628
629 CreateOrUpdateViews(notification);
630 UpdateControlButtonsVisibilityWithNotification(notification);
631
632 set_notify_enter_exit_on_child(true);
633
634 click_activator_ = std::make_unique<ClickActivator>(this);
635 // Reasons to use pretarget handler instead of OnMousePressed:
636 // - NotificationViewMD::OnMousePresssed would not fire on the inline reply
637 // textfield click in native notification.
638 // - To make it look similar to ArcNotificationContentView::EventForwarder.
639 AddPreTargetHandler(click_activator_.get());
640
641 auto highlight_path_generator =
642 std::make_unique<NotificationViewMDPathGenerator>();
643 highlight_path_generator_ = highlight_path_generator.get();
644 views::HighlightPathGenerator::Install(this,
645 std::move(highlight_path_generator));
646
647 UpdateCornerRadius(kNotificationCornerRadius, kNotificationCornerRadius);
648 }
649
~NotificationViewMD()650 NotificationViewMD::~NotificationViewMD() {
651 RemovePreTargetHandler(click_activator_.get());
652 }
653
AddLayerBeneathView(ui::Layer * layer)654 void NotificationViewMD::AddLayerBeneathView(ui::Layer* layer) {
655 GetInkDrop()->AddObserver(this);
656 for (auto* child : GetChildrenForLayerAdjustment()) {
657 child->SetPaintToLayer();
658 child->layer()->SetFillsBoundsOpaquely(false);
659 }
660 ink_drop_container_->AddLayerBeneathView(layer);
661 }
662
RemoveLayerBeneathView(ui::Layer * layer)663 void NotificationViewMD::RemoveLayerBeneathView(ui::Layer* layer) {
664 ink_drop_container_->RemoveLayerBeneathView(layer);
665 for (auto* child : GetChildrenForLayerAdjustment())
666 child->DestroyLayer();
667 GetInkDrop()->RemoveObserver(this);
668 }
669
Layout()670 void NotificationViewMD::Layout() {
671 MessageView::Layout();
672
673 // We need to call IsExpandable() at the end of Layout() call, since whether
674 // we should show expand button or not depends on the current view layout.
675 // (e.g. Show expand button when |message_view_| exceeds one line.)
676 header_row_->SetExpandButtonEnabled(IsExpandable());
677 header_row_->Layout();
678
679 // The notification background is rounded in MessageView::Layout(),
680 // but we also have to round the actions row background here.
681 if (actions_row_->GetVisible()) {
682 constexpr SkScalar kCornerRadius = SkIntToScalar(kNotificationCornerRadius);
683
684 // Use vertically larger clip path, so that actions row's top coners will
685 // not be rounded.
686 SkPath path;
687 gfx::Rect bounds = actions_row_->GetLocalBounds();
688 bounds.set_y(bounds.y() - bounds.height());
689 bounds.set_height(bounds.height() * 2);
690 path.addRoundRect(gfx::RectToSkRect(bounds), kCornerRadius, kCornerRadius);
691
692 action_buttons_row_->SetClipPath(path);
693 inline_reply_->SetClipPath(path);
694 }
695
696 // The animation is needed to run inside of the border.
697 ink_drop_container_->SetBoundsRect(GetLocalBounds());
698 }
699
OnFocus()700 void NotificationViewMD::OnFocus() {
701 MessageView::OnFocus();
702 ScrollRectToVisible(GetLocalBounds());
703 }
704
OnMousePressed(const ui::MouseEvent & event)705 bool NotificationViewMD::OnMousePressed(const ui::MouseEvent& event) {
706 last_mouse_pressed_timestamp_ = base::TimeTicks(event.time_stamp());
707 return true;
708 }
709
OnMouseDragged(const ui::MouseEvent & event)710 bool NotificationViewMD::OnMouseDragged(const ui::MouseEvent& event) {
711 return true;
712 }
713
OnMouseReleased(const ui::MouseEvent & event)714 void NotificationViewMD::OnMouseReleased(const ui::MouseEvent& event) {
715 if (!event.IsOnlyLeftMouseButton())
716 return;
717
718 // The mouse has been clicked for a long time.
719 if (ui::EventTimeStampToSeconds(event.time_stamp()) -
720 ui::EventTimeStampToSeconds(last_mouse_pressed_timestamp_) >
721 ui::GetGestureProviderConfig(
722 ui::GestureProviderConfigType::CURRENT_PLATFORM)
723 .gesture_detector_config.longpress_timeout.InSecondsF()) {
724 ToggleInlineSettings(event);
725 return;
726 }
727
728 // Ignore click of actions row outside action buttons.
729 if (expanded_) {
730 DCHECK(actions_row_);
731 gfx::Point point_in_child = event.location();
732 ConvertPointToTarget(this, actions_row_, &point_in_child);
733 if (actions_row_->HitTestPoint(point_in_child))
734 return;
735 }
736
737 // Ignore clicks of outside region when inline settings is shown.
738 if (settings_row_ && settings_row_->GetVisible())
739 return;
740
741 MessageView::OnMouseReleased(event);
742 }
743
OnMouseEvent(ui::MouseEvent * event)744 void NotificationViewMD::OnMouseEvent(ui::MouseEvent* event) {
745 switch (event->type()) {
746 case ui::ET_MOUSE_ENTERED:
747 UpdateControlButtonsVisibility();
748 break;
749 case ui::ET_MOUSE_EXITED:
750 UpdateControlButtonsVisibility();
751 break;
752 default:
753 break;
754 }
755 View::OnMouseEvent(event);
756 }
757
OnGestureEvent(ui::GestureEvent * event)758 void NotificationViewMD::OnGestureEvent(ui::GestureEvent* event) {
759 if (event->type() == ui::ET_GESTURE_LONG_TAP) {
760 ToggleInlineSettings(*event);
761 return;
762 }
763 MessageView::OnGestureEvent(event);
764 }
765
PreferredSizeChanged()766 void NotificationViewMD::PreferredSizeChanged() {
767 highlight_path_generator_->set_preferred_size(GetPreferredSize());
768 MessageView::PreferredSizeChanged();
769 }
770
UpdateWithNotification(const Notification & notification)771 void NotificationViewMD::UpdateWithNotification(
772 const Notification& notification) {
773 MessageView::UpdateWithNotification(notification);
774 UpdateControlButtonsVisibilityWithNotification(notification);
775
776 CreateOrUpdateViews(notification);
777 Layout();
778 SchedulePaint();
779 }
780
781 // TODO(yoshiki): Move this to the parent class (MessageView).
UpdateControlButtonsVisibilityWithNotification(const Notification & notification)782 void NotificationViewMD::UpdateControlButtonsVisibilityWithNotification(
783 const Notification& notification) {
784 control_buttons_view_->ShowSettingsButton(
785 notification.should_show_settings_button());
786 control_buttons_view_->ShowSnoozeButton(
787 notification.should_show_snooze_button());
788 control_buttons_view_->ShowCloseButton(GetMode() != Mode::PINNED);
789 UpdateControlButtonsVisibility();
790 }
791
ButtonPressed(views::Button * sender,const ui::Event & event)792 void NotificationViewMD::ButtonPressed(views::Button* sender,
793 const ui::Event& event) {
794 // Tapping anywhere on |header_row_| can expand the notification, though only
795 // |expand_button| can be focused by TAB.
796 if (sender == header_row_) {
797 if (IsExpandable() && content_row_->GetVisible()) {
798 SetManuallyExpandedOrCollapsed(true);
799 auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
800 ToggleExpanded();
801 // Check |this| is valid before continuing, because ToggleExpanded() might
802 // cause |this| to be deleted.
803 if (!weak_ptr)
804 return;
805 Layout();
806 SchedulePaint();
807 }
808 return;
809 }
810
811 // See if the button pressed was an action button.
812 for (size_t i = 0; i < action_buttons_.size(); ++i) {
813 if (sender != action_buttons_[i])
814 continue;
815
816 const base::Optional<base::string16>& placeholder =
817 action_buttons_[i]->placeholder();
818 if (placeholder) {
819 inline_reply_->textfield()->SetProperty(kTextfieldIndexKey,
820 static_cast<int>(i));
821 inline_reply_->textfield()->SetPlaceholderText(
822 placeholder->empty()
823 ? l10n_util::GetStringUTF16(
824 IDS_MESSAGE_CENTER_NOTIFICATION_INLINE_REPLY_PLACEHOLDER)
825 : *placeholder);
826 inline_reply_->AnimateBackground(event);
827 inline_reply_->SetVisible(true);
828 action_buttons_row_->SetVisible(false);
829 // RequestFocus() should be called after SetVisible().
830 inline_reply_->textfield()->RequestFocus();
831 Layout();
832 SchedulePaint();
833 } else {
834 MessageCenter::Get()->ClickOnNotificationButton(notification_id(), i);
835 }
836 return;
837 }
838
839 if (sender == settings_done_button_) {
840 ToggleInlineSettings(event);
841 return;
842 }
843 }
844
OnNotificationInputSubmit(size_t index,const base::string16 & text)845 void NotificationViewMD::OnNotificationInputSubmit(size_t index,
846 const base::string16& text) {
847 MessageCenter::Get()->ClickOnNotificationButtonWithReply(notification_id(),
848 index, text);
849 }
850
CreateOrUpdateContextTitleView(const Notification & notification)851 void NotificationViewMD::CreateOrUpdateContextTitleView(
852 const Notification& notification) {
853 header_row_->SetAccentColor(notification.accent_color() == SK_ColorTRANSPARENT
854 ? kNotificationDefaultAccentColor
855 : notification.accent_color());
856 header_row_->SetBackgroundColor(kNotificationBackgroundColor);
857 header_row_->SetTimestamp(notification.timestamp());
858 header_row_->SetAppNameElideBehavior(gfx::ELIDE_TAIL);
859 header_row_->SetSummaryText(base::string16());
860
861 base::string16 app_name;
862 if (notification.UseOriginAsContextMessage()) {
863 app_name = url_formatter::FormatUrlForSecurityDisplay(
864 notification.origin_url(),
865 url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
866 header_row_->SetAppNameElideBehavior(gfx::ELIDE_HEAD);
867 } else if (notification.display_source().empty() &&
868 notification.notifier_id().type ==
869 NotifierType::SYSTEM_COMPONENT) {
870 app_name = MessageCenter::Get()->GetSystemNotificationAppName();
871 } else if (!notification.context_message().empty()) {
872 app_name = notification.context_message();
873 } else {
874 app_name = notification.display_source();
875 }
876 header_row_->SetAppName(app_name);
877 }
878
CreateOrUpdateTitleView(const Notification & notification)879 void NotificationViewMD::CreateOrUpdateTitleView(
880 const Notification& notification) {
881 if (notification.title().empty() ||
882 notification.type() == NOTIFICATION_TYPE_PROGRESS) {
883 DCHECK(!title_view_ || left_content_->Contains(title_view_));
884 delete title_view_;
885 title_view_ = nullptr;
886 return;
887 }
888
889 int title_character_limit =
890 kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacterMD;
891
892 base::string16 title = gfx::TruncateString(
893 notification.title(), title_character_limit, gfx::WORD_BREAK);
894 if (!title_view_) {
895 const gfx::FontList& font_list = GetTextFontList();
896
897 title_view_ = new views::Label(title);
898 title_view_->SetFontList(font_list);
899 title_view_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
900 title_view_->SetEnabledColor(kRegularTextColorMD);
901 title_view_->SetBackgroundColor(kNotificationBackgroundColor);
902 title_view_->SetLineHeight(kLineHeightMD);
903 // TODO(knollr): multiline should not be required, but we need to set the
904 // width of |title_view_| (because of crbug.com/682266), which only works in
905 // multiline mode.
906 title_view_->SetMultiLine(true);
907 title_view_->SetMaxLines(kMaxLinesForTitleView);
908 title_view_->SetAllowCharacterBreak(true);
909 left_content_->AddChildViewAt(title_view_, left_content_count_);
910 } else {
911 title_view_->SetText(title);
912 }
913
914 left_content_count_++;
915 }
916
CreateOrUpdateMessageView(const Notification & notification)917 void NotificationViewMD::CreateOrUpdateMessageView(
918 const Notification& notification) {
919 if (notification.type() == NOTIFICATION_TYPE_PROGRESS ||
920 notification.message().empty()) {
921 // Deletion will also remove |message_view_| from its parent.
922 delete message_view_;
923 message_view_ = nullptr;
924 return;
925 }
926
927 base::string16 text = gfx::TruncateString(
928 notification.message(), kMessageCharacterLimitMD, gfx::WORD_BREAK);
929
930 if (!message_view_) {
931 const gfx::FontList& font_list = GetTextFontList();
932
933 message_view_ = new views::Label(text);
934 message_view_->SetFontList(font_list);
935 message_view_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
936 message_view_->SetEnabledColor(kDimTextColorMD);
937 message_view_->SetBackgroundColor(kNotificationBackgroundColor);
938 message_view_->SetLineHeight(kLineHeightMD);
939 message_view_->SetMultiLine(true);
940 message_view_->SetMaxLines(kMaxLinesForMessageView);
941 message_view_->SetAllowCharacterBreak(true);
942 left_content_->AddChildViewAt(message_view_, left_content_count_);
943 } else {
944 message_view_->SetText(text);
945 }
946
947 message_view_->SetVisible(notification.items().empty());
948 left_content_count_++;
949 }
950
CreateOrUpdateCompactTitleMessageView(const Notification & notification)951 void NotificationViewMD::CreateOrUpdateCompactTitleMessageView(
952 const Notification& notification) {
953 if (notification.type() != NOTIFICATION_TYPE_PROGRESS) {
954 DCHECK(!compact_title_message_view_ ||
955 left_content_->Contains(compact_title_message_view_));
956 delete compact_title_message_view_;
957 compact_title_message_view_ = nullptr;
958 return;
959 }
960 if (!compact_title_message_view_) {
961 compact_title_message_view_ = new CompactTitleMessageView();
962 left_content_->AddChildViewAt(compact_title_message_view_,
963 left_content_count_);
964 }
965
966 compact_title_message_view_->set_title(notification.title());
967 compact_title_message_view_->set_message(notification.message());
968 left_content_->InvalidateLayout();
969 left_content_count_++;
970 }
971
CreateOrUpdateProgressBarView(const Notification & notification)972 void NotificationViewMD::CreateOrUpdateProgressBarView(
973 const Notification& notification) {
974 if (notification.type() != NOTIFICATION_TYPE_PROGRESS) {
975 DCHECK(!progress_bar_view_ || left_content_->Contains(progress_bar_view_));
976 delete progress_bar_view_;
977 progress_bar_view_ = nullptr;
978 return;
979 }
980
981 DCHECK(left_content_);
982
983 if (!progress_bar_view_) {
984 progress_bar_view_ = new views::ProgressBar(kProgressBarHeight,
985 /* allow_round_corner */ false);
986 progress_bar_view_->SetBorder(
987 views::CreateEmptyBorder(kProgressBarTopPadding, 0, 0, 0));
988 progress_bar_view_->SetForegroundColor(kActionButtonTextColor);
989 left_content_->AddChildViewAt(progress_bar_view_, left_content_count_);
990 }
991
992 progress_bar_view_->SetValue(notification.progress() / 100.0);
993 progress_bar_view_->SetVisible(notification.items().empty());
994
995 if (0 <= notification.progress() && notification.progress() <= 100)
996 header_row_->SetProgress(notification.progress());
997
998 left_content_count_++;
999 }
1000
CreateOrUpdateProgressStatusView(const Notification & notification)1001 void NotificationViewMD::CreateOrUpdateProgressStatusView(
1002 const Notification& notification) {
1003 if (notification.type() != NOTIFICATION_TYPE_PROGRESS ||
1004 notification.progress_status().empty()) {
1005 if (!status_view_)
1006 return;
1007 DCHECK(left_content_->Contains(status_view_));
1008 delete status_view_;
1009 status_view_ = nullptr;
1010 return;
1011 }
1012
1013 if (!status_view_) {
1014 const gfx::FontList& font_list = GetTextFontList();
1015 status_view_ = new views::Label();
1016 status_view_->SetFontList(font_list);
1017 status_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1018 status_view_->SetEnabledColor(kDimTextColorMD);
1019 status_view_->SetBackgroundColor(kNotificationBackgroundColor);
1020 status_view_->SetBorder(views::CreateEmptyBorder(kStatusTextPadding));
1021 left_content_->AddChildViewAt(status_view_, left_content_count_);
1022 }
1023
1024 status_view_->SetText(notification.progress_status());
1025 left_content_count_++;
1026 }
1027
CreateOrUpdateListItemViews(const Notification & notification)1028 void NotificationViewMD::CreateOrUpdateListItemViews(
1029 const Notification& notification) {
1030 for (auto* item_view : item_views_)
1031 delete item_view;
1032 item_views_.clear();
1033
1034 const std::vector<NotificationItem>& items = notification.items();
1035
1036 for (size_t i = 0; i < items.size() && i < kMaxLinesForExpandedMessageView;
1037 ++i) {
1038 std::unique_ptr<views::View> item_view = CreateItemView(items[i]);
1039 item_views_.push_back(item_view.get());
1040 left_content_->AddChildViewAt(item_view.release(), left_content_count_++);
1041 }
1042
1043 list_items_count_ = items.size();
1044
1045 // Needed when CreateOrUpdateViews is called for update.
1046 if (!item_views_.empty())
1047 left_content_->InvalidateLayout();
1048 }
1049
CreateOrUpdateIconView(const Notification & notification)1050 void NotificationViewMD::CreateOrUpdateIconView(
1051 const Notification& notification) {
1052 const bool use_image_for_icon = notification.icon().IsEmpty();
1053
1054 gfx::ImageSkia icon = use_image_for_icon ? notification.image().AsImageSkia()
1055 : notification.icon().AsImageSkia();
1056
1057 if (notification.type() == NOTIFICATION_TYPE_PROGRESS ||
1058 notification.type() == NOTIFICATION_TYPE_MULTIPLE || icon.isNull()) {
1059 DCHECK(!icon_view_ || right_content_->Contains(icon_view_));
1060 delete icon_view_;
1061 icon_view_ = nullptr;
1062 return;
1063 }
1064
1065 if (!icon_view_) {
1066 icon_view_ = new ProportionalImageView(kIconViewSize);
1067 right_content_->AddChildView(icon_view_);
1068 }
1069
1070 icon_view_->SetImage(icon, icon.size());
1071
1072 // Hide the icon on the right side when the notification is expanded.
1073 hide_icon_on_expanded_ = use_image_for_icon;
1074 }
1075
CreateOrUpdateSmallIconView(const Notification & notification)1076 void NotificationViewMD::CreateOrUpdateSmallIconView(
1077 const Notification& notification) {
1078 // TODO(knollr): figure out if this has a performance impact and
1079 // cache images if so. (crbug.com/768748)
1080 gfx::Image masked_small_icon = notification.GenerateMaskedSmallIcon(
1081 kSmallImageSizeMD, notification.accent_color() == SK_ColorTRANSPARENT
1082 ? message_center::kNotificationDefaultAccentColor
1083 : notification.accent_color());
1084
1085 if (masked_small_icon.IsEmpty()) {
1086 header_row_->ClearAppIcon();
1087 } else {
1088 header_row_->SetAppIcon(masked_small_icon.AsImageSkia());
1089 }
1090 }
1091
CreateOrUpdateImageView(const Notification & notification)1092 void NotificationViewMD::CreateOrUpdateImageView(
1093 const Notification& notification) {
1094 if (notification.image().IsEmpty()) {
1095 if (image_container_view_) {
1096 DCHECK(Contains(image_container_view_));
1097 delete image_container_view_;
1098 image_container_view_ = nullptr;
1099 }
1100 return;
1101 }
1102
1103 if (!image_container_view_) {
1104 image_container_view_ = new views::View();
1105 image_container_view_->SetLayoutManager(
1106 std::make_unique<views::FillLayout>());
1107 image_container_view_->SetBorder(
1108 views::CreateEmptyBorder(kLargeImageContainerPadding));
1109 image_container_view_->SetBackground(
1110 views::CreateSolidBackground(kImageBackgroundColor));
1111 image_container_view_->AddChildView(new LargeImageView());
1112
1113 // Insert the created image container just after the |content_row_|.
1114 AddChildViewAt(image_container_view_, GetIndexOf(content_row_) + 1);
1115 }
1116
1117 static_cast<LargeImageView*>(image_container_view_->children().front())
1118 ->SetImage(notification.image().AsImageSkia());
1119 }
1120
CreateOrUpdateActionButtonViews(const Notification & notification)1121 void NotificationViewMD::CreateOrUpdateActionButtonViews(
1122 const Notification& notification) {
1123 const std::vector<ButtonInfo>& buttons = notification.buttons();
1124 bool new_buttons = action_buttons_.size() != buttons.size();
1125
1126 if (new_buttons || buttons.empty()) {
1127 for (auto* item : action_buttons_)
1128 delete item;
1129 action_buttons_.clear();
1130 actions_row_->SetVisible(expanded_ && !buttons.empty());
1131 }
1132
1133 DCHECK_EQ(this, actions_row_->parent());
1134
1135 // Hide inline reply field if it doesn't exist anymore.
1136 if (inline_reply_->GetVisible()) {
1137 const size_t index =
1138 inline_reply_->textfield()->GetProperty(kTextfieldIndexKey);
1139 if (index >= buttons.size() || !buttons[index].placeholder.has_value()) {
1140 action_buttons_row_->SetVisible(true);
1141 inline_reply_->SetVisible(false);
1142 }
1143 }
1144
1145 for (size_t i = 0; i < buttons.size(); ++i) {
1146 ButtonInfo button_info = buttons[i];
1147 if (new_buttons) {
1148 NotificationButtonMD* button = new NotificationButtonMD(
1149 this, button_info.title, button_info.placeholder);
1150 action_buttons_.push_back(button);
1151 action_buttons_row_->AddChildView(button);
1152 } else {
1153 action_buttons_[i]->SetText(button_info.title);
1154 action_buttons_[i]->set_placeholder(button_info.placeholder);
1155 action_buttons_[i]->SchedulePaint();
1156 action_buttons_[i]->Layout();
1157 }
1158
1159 // Change action button color to the accent color.
1160 action_buttons_[i]->SetEnabledTextColors(notification.accent_color() ==
1161 SK_ColorTRANSPARENT
1162 ? kActionButtonTextColor
1163 : notification.accent_color());
1164 }
1165
1166 // Inherit mouse hover state when action button views reset.
1167 // If the view is not expanded, there should be no hover state.
1168 if (new_buttons && expanded_) {
1169 views::Widget* widget = GetWidget();
1170 if (widget) {
1171 // This Layout() is needed because button should be in the right location
1172 // in the view hierarchy when SynthesizeMouseMoveEvent() is called.
1173 Layout();
1174 widget->SetSize(widget->GetContentsView()->GetPreferredSize());
1175 GetWidget()->SynthesizeMouseMoveEvent();
1176 }
1177 }
1178 }
1179
CreateOrUpdateInlineSettingsViews(const Notification & notification)1180 void NotificationViewMD::CreateOrUpdateInlineSettingsViews(
1181 const Notification& notification) {
1182 if (settings_row_) {
1183 DCHECK_EQ(SettingsButtonHandler::INLINE,
1184 notification.rich_notification_data().settings_button_handler);
1185 return;
1186 }
1187
1188 if (notification.rich_notification_data().settings_button_handler !=
1189 SettingsButtonHandler::INLINE) {
1190 return;
1191 }
1192
1193 // |settings_row_| contains inline settings.
1194 settings_row_ = new views::View();
1195 settings_row_->SetLayoutManager(std::make_unique<views::BoxLayout>(
1196 views::BoxLayout::Orientation::kVertical, kSettingsRowPadding, 0));
1197
1198 int block_notifications_message_id = 0;
1199 switch (notification.notifier_id().type) {
1200 case NotifierType::APPLICATION:
1201 case NotifierType::ARC_APPLICATION:
1202 block_notifications_message_id =
1203 IDS_MESSAGE_CENTER_BLOCK_ALL_NOTIFICATIONS_APP;
1204 break;
1205 case NotifierType::WEB_PAGE:
1206 block_notifications_message_id =
1207 IDS_MESSAGE_CENTER_BLOCK_ALL_NOTIFICATIONS_SITE;
1208 break;
1209 case NotifierType::SYSTEM_COMPONENT:
1210 block_notifications_message_id =
1211 IDS_MESSAGE_CENTER_BLOCK_ALL_NOTIFICATIONS;
1212 break;
1213 case NotifierType::CROSTINI_APPLICATION:
1214 NOTREACHED();
1215 break;
1216 }
1217 DCHECK_NE(block_notifications_message_id, 0);
1218
1219 block_all_button_ = new InlineSettingsRadioButton(
1220 l10n_util::GetStringUTF16(block_notifications_message_id));
1221 block_all_button_->SetBorder(
1222 views::CreateEmptyBorder(kSettingsRadioButtonPadding));
1223 settings_row_->AddChildView(block_all_button_);
1224
1225 dont_block_button_ = new InlineSettingsRadioButton(
1226 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_DONT_BLOCK_NOTIFICATIONS));
1227 dont_block_button_->SetBorder(
1228 views::CreateEmptyBorder(kSettingsRadioButtonPadding));
1229 settings_row_->AddChildView(dont_block_button_);
1230 settings_row_->SetVisible(false);
1231
1232 settings_done_button_ = new NotificationButtonMD(
1233 this, l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_SETTINGS_DONE),
1234 base::nullopt);
1235 settings_done_button_->SetTextSubpixelRenderingEnabled(false);
1236
1237 auto* settings_button_row = new views::View;
1238 auto settings_button_layout = std::make_unique<views::BoxLayout>(
1239 views::BoxLayout::Orientation::kHorizontal, kSettingsButtonRowPadding, 0);
1240 settings_button_layout->set_main_axis_alignment(
1241 views::BoxLayout::MainAxisAlignment::kEnd);
1242 settings_button_row->SetLayoutManager(std::move(settings_button_layout));
1243 settings_button_row->AddChildView(settings_done_button_);
1244 settings_row_->AddChildView(settings_button_row);
1245
1246 AddChildViewAt(settings_row_, GetIndexOf(actions_row_));
1247 }
1248
IsExpandable()1249 bool NotificationViewMD::IsExpandable() {
1250 // Inline settings can not be expanded.
1251 if (GetMode() == Mode::SETTING)
1252 return false;
1253
1254 // Expandable if the message exceeds one line.
1255 if (message_view_ && message_view_->GetVisible() &&
1256 message_view_->GetRequiredLines() > 1) {
1257 return true;
1258 }
1259 // Expandable if there is at least one inline action.
1260 if (!action_buttons_row_->children().empty())
1261 return true;
1262
1263 // Expandable if the notification has image.
1264 if (image_container_view_)
1265 return true;
1266
1267 // Expandable if there are multiple list items.
1268 if (item_views_.size() > 1)
1269 return true;
1270
1271 // Expandable if both progress bar and status message exist.
1272 if (status_view_)
1273 return true;
1274
1275 return false;
1276 }
1277
ToggleExpanded()1278 void NotificationViewMD::ToggleExpanded() {
1279 SetExpanded(!expanded_);
1280 }
1281
UpdateViewForExpandedState(bool expanded)1282 void NotificationViewMD::UpdateViewForExpandedState(bool expanded) {
1283 header_row_->SetExpanded(expanded);
1284 if (message_view_) {
1285 message_view_->SetMaxLines(expanded ? kMaxLinesForExpandedMessageView
1286 : kMaxLinesForMessageView);
1287 }
1288 if (image_container_view_)
1289 image_container_view_->SetVisible(expanded);
1290
1291 actions_row_->SetVisible(expanded &&
1292 !action_buttons_row_->children().empty());
1293 if (!expanded) {
1294 action_buttons_row_->SetVisible(true);
1295 inline_reply_->SetVisible(false);
1296 }
1297
1298 for (size_t i = kMaxLinesForMessageView; i < item_views_.size(); ++i) {
1299 item_views_[i]->SetVisible(expanded);
1300 }
1301 if (status_view_)
1302 status_view_->SetVisible(expanded);
1303
1304 int max_items = expanded ? item_views_.size() : kMaxLinesForMessageView;
1305 if (list_items_count_ > max_items)
1306 header_row_->SetOverflowIndicator(list_items_count_ - max_items);
1307 else if (!item_views_.empty())
1308 header_row_->SetSummaryText(base::string16());
1309
1310 bool has_icon = icon_view_ && (!hide_icon_on_expanded_ || !expanded);
1311 right_content_->SetVisible(has_icon);
1312 left_content_->SetBorder(views::CreateEmptyBorder(
1313 has_icon ? kLeftContentPaddingWithIcon : kLeftContentPadding));
1314
1315 // TODO(tetsui): Workaround https://crbug.com/682266 by explicitly setting
1316 // the width.
1317 // Ideally, we should fix the original bug, but it seems there's no obvious
1318 // solution for the bug according to https://crbug.com/678337#c7, we should
1319 // ensure that the change won't break any of the users of BoxLayout class.
1320 const int message_view_width =
1321 (has_icon ? kMessageViewWidthWithIcon : kMessageViewWidth) -
1322 GetInsets().width();
1323 if (title_view_)
1324 title_view_->SizeToFit(message_view_width);
1325 if (message_view_)
1326 message_view_->SizeToFit(message_view_width);
1327
1328 content_row_->InvalidateLayout();
1329 }
1330
ToggleInlineSettings(const ui::Event & event)1331 void NotificationViewMD::ToggleInlineSettings(const ui::Event& event) {
1332 if (!settings_row_)
1333 return;
1334
1335 bool inline_settings_visible = !settings_row_->GetVisible();
1336 bool disable_notification =
1337 settings_row_->GetVisible() && block_all_button_->GetChecked();
1338
1339 settings_row_->SetVisible(inline_settings_visible);
1340 content_row_->SetVisible(!inline_settings_visible);
1341 header_row_->SetDetailViewsVisible(!inline_settings_visible);
1342 header_row_->SetBackgroundColor(inline_settings_visible
1343 ? kInlineSettingsBackgroundColor
1344 : kNotificationBackgroundColor);
1345
1346 // Always check "Don't block" when inline settings is shown.
1347 // If it's already blocked, users should not see inline settings.
1348 // Toggling should reset the state.
1349 dont_block_button_->SetChecked(true);
1350
1351 SetSettingMode(inline_settings_visible);
1352
1353 // Grab a weak pointer before calling SetExpanded() as it might cause |this|
1354 // to be deleted.
1355 {
1356 auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
1357 SetExpanded(!inline_settings_visible);
1358 if (!weak_ptr)
1359 return;
1360 }
1361
1362 PreferredSizeChanged();
1363
1364 if (inline_settings_visible)
1365 AddBackgroundAnimation(event);
1366 else
1367 RemoveBackgroundAnimation();
1368
1369 Layout();
1370 SchedulePaint();
1371
1372 // Call DisableNotification() at the end, because |this| can be deleted at any
1373 // point after it's called.
1374 if (disable_notification)
1375 MessageCenter::Get()->DisableNotification(notification_id());
1376 }
1377
UpdateCornerRadius(int top_radius,int bottom_radius)1378 void NotificationViewMD::UpdateCornerRadius(int top_radius, int bottom_radius) {
1379 MessageView::UpdateCornerRadius(top_radius, bottom_radius);
1380 action_buttons_row_->SetBackground(views::CreateBackgroundFromPainter(
1381 std::make_unique<NotificationBackgroundPainter>(
1382 0, bottom_radius, kActionsRowBackgroundColor)));
1383 highlight_path_generator_->set_top_radius(top_radius);
1384 highlight_path_generator_->set_bottom_radius(bottom_radius);
1385 }
1386
GetControlButtonsView() const1387 NotificationControlButtonsView* NotificationViewMD::GetControlButtonsView()
1388 const {
1389 return control_buttons_view_.get();
1390 }
1391
IsExpanded() const1392 bool NotificationViewMD::IsExpanded() const {
1393 return expanded_;
1394 }
1395
SetExpanded(bool expanded)1396 void NotificationViewMD::SetExpanded(bool expanded) {
1397 if (expanded_ == expanded)
1398 return;
1399 expanded_ = expanded;
1400
1401 UpdateViewForExpandedState(expanded_);
1402 PreferredSizeChanged();
1403 }
1404
IsManuallyExpandedOrCollapsed() const1405 bool NotificationViewMD::IsManuallyExpandedOrCollapsed() const {
1406 return manually_expanded_or_collapsed_;
1407 }
1408
SetManuallyExpandedOrCollapsed(bool value)1409 void NotificationViewMD::SetManuallyExpandedOrCollapsed(bool value) {
1410 manually_expanded_or_collapsed_ = value;
1411 }
1412
OnSettingsButtonPressed(const ui::Event & event)1413 void NotificationViewMD::OnSettingsButtonPressed(const ui::Event& event) {
1414 for (auto& observer : *observers())
1415 observer.OnSettingsButtonPressed(notification_id());
1416
1417 if (settings_row_)
1418 ToggleInlineSettings(event);
1419 else
1420 MessageView::OnSettingsButtonPressed(event);
1421 }
1422
Activate()1423 void NotificationViewMD::Activate() {
1424 GetWidget()->widget_delegate()->SetCanActivate(true);
1425 GetWidget()->Activate();
1426 }
1427
AddBackgroundAnimation(const ui::Event & event)1428 void NotificationViewMD::AddBackgroundAnimation(const ui::Event& event) {
1429 SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER);
1430 // In case the animation is triggered from keyboard operation.
1431 if (!event.IsLocatedEvent()) {
1432 AnimateInkDrop(views::InkDropState::ACTION_PENDING, nullptr);
1433 return;
1434 }
1435
1436 // Convert the point of |event| from the coordinate system of
1437 // |control_buttons_view_| to that of NotificationViewMD, create a new
1438 // LocatedEvent which has the new point.
1439 views::View* target = static_cast<views::View*>(event.target());
1440 const gfx::Point& location = event.AsLocatedEvent()->location();
1441 gfx::Point converted_location(location);
1442 View::ConvertPointToTarget(target, this, &converted_location);
1443
1444 // Use default animation if location is out of bounds.
1445 if (!View::HitTestPoint(converted_location)) {
1446 AnimateInkDrop(views::InkDropState::ACTION_PENDING, nullptr);
1447 return;
1448 }
1449
1450 std::unique_ptr<ui::Event> cloned_event = ui::Event::Clone(event);
1451 ui::LocatedEvent* cloned_located_event = cloned_event->AsLocatedEvent();
1452 cloned_located_event->set_location(converted_location);
1453 AnimateInkDrop(views::InkDropState::ACTION_PENDING, cloned_located_event);
1454 }
1455
RemoveBackgroundAnimation()1456 void NotificationViewMD::RemoveBackgroundAnimation() {
1457 SetInkDropMode(InkDropMode::OFF);
1458 AnimateInkDrop(views::InkDropState::HIDDEN, nullptr);
1459 }
1460
CreateInkDrop()1461 std::unique_ptr<views::InkDrop> NotificationViewMD::CreateInkDrop() {
1462 return std::make_unique<NotificationInkDropImpl>(this, size());
1463 }
1464
CreateInkDropRipple() const1465 std::unique_ptr<views::InkDropRipple> NotificationViewMD::CreateInkDropRipple()
1466 const {
1467 return std::make_unique<views::FloodFillInkDropRipple>(
1468 GetPreferredSize(), GetInkDropCenterBasedOnLastEvent(),
1469 GetInkDropBaseColor(), ink_drop_visible_opacity());
1470 }
1471
GetChildrenForLayerAdjustment() const1472 std::vector<views::View*> NotificationViewMD::GetChildrenForLayerAdjustment()
1473 const {
1474 return {header_row_, block_all_button_, dont_block_button_,
1475 settings_done_button_};
1476 }
1477
CreateInkDropMask() const1478 std::unique_ptr<views::InkDropMask> NotificationViewMD::CreateInkDropMask()
1479 const {
1480 return nullptr;
1481 }
1482
GetInkDropBaseColor() const1483 SkColor NotificationViewMD::GetInkDropBaseColor() const {
1484 return kInlineSettingsBackgroundColor;
1485 }
1486
InkDropAnimationStarted()1487 void NotificationViewMD::InkDropAnimationStarted() {
1488 header_row_->SetSubpixelRenderingEnabled(false);
1489 }
1490
InkDropRippleAnimationEnded(views::InkDropState ink_drop_state)1491 void NotificationViewMD::InkDropRippleAnimationEnded(
1492 views::InkDropState ink_drop_state) {
1493 if (ink_drop_state == views::InkDropState::HIDDEN)
1494 header_row_->SetSubpixelRenderingEnabled(true);
1495 }
1496
1497 } // namespace message_center
1498