ash_notification_view.cc 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922
  1. // Copyright 2021 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. #include "ash/system/message_center/ash_notification_view.h"
  5. #include <memory>
  6. #include <utility>
  7. #include "ash/public/cpp/metrics_util.h"
  8. #include "ash/public/cpp/rounded_image_view.h"
  9. #include "ash/public/cpp/style/color_provider.h"
  10. #include "ash/resources/vector_icons/vector_icons.h"
  11. #include "ash/root_window_controller.h"
  12. #include "ash/shelf/shelf.h"
  13. #include "ash/shell.h"
  14. #include "ash/strings/grit/ash_strings.h"
  15. #include "ash/style/ash_color_provider.h"
  16. #include "ash/style/dark_light_mode_controller_impl.h"
  17. #include "ash/style/icon_button.h"
  18. #include "ash/style/pill_button.h"
  19. #include "ash/system/message_center/ash_notification_expand_button.h"
  20. #include "ash/system/message_center/ash_notification_input_container.h"
  21. #include "ash/system/message_center/message_center_constants.h"
  22. #include "ash/system/message_center/message_center_style.h"
  23. #include "ash/system/message_center/message_center_utils.h"
  24. #include "ash/system/message_center/metrics_utils.h"
  25. #include "ash/system/message_center/notification_grouping_controller.h"
  26. #include "ash/wm/work_area_insets.h"
  27. #include "base/bind.h"
  28. #include "base/check.h"
  29. #include "base/metrics/histogram_functions.h"
  30. #include "base/time/time.h"
  31. #include "ui/base/l10n/l10n_util.h"
  32. #include "ui/base/metadata/metadata_impl_macros.h"
  33. #include "ui/color/color_id.h"
  34. #include "ui/color/color_provider.h"
  35. #include "ui/compositor/animation_throughput_reporter.h"
  36. #include "ui/compositor/layer.h"
  37. #include "ui/compositor/layer_animator.h"
  38. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  39. #include "ui/gfx/animation/tween.h"
  40. #include "ui/gfx/color_utils.h"
  41. #include "ui/gfx/font.h"
  42. #include "ui/gfx/geometry/insets.h"
  43. #include "ui/gfx/geometry/rounded_corners_f.h"
  44. #include "ui/gfx/geometry/size.h"
  45. #include "ui/gfx/geometry/transform.h"
  46. #include "ui/gfx/image/image_skia_operations.h"
  47. #include "ui/gfx/paint_vector_icon.h"
  48. #include "ui/gfx/text_constants.h"
  49. #include "ui/gfx/text_elider.h"
  50. #include "ui/message_center/message_center.h"
  51. #include "ui/message_center/notification_view_controller.h"
  52. #include "ui/message_center/public/cpp/message_center_constants.h"
  53. #include "ui/message_center/public/cpp/notification.h"
  54. #include "ui/message_center/vector_icons.h"
  55. #include "ui/message_center/views/notification_background_painter.h"
  56. #include "ui/message_center/views/notification_control_buttons_view.h"
  57. #include "ui/message_center/views/notification_header_view.h"
  58. #include "ui/message_center/views/notification_view_base.h"
  59. #include "ui/message_center/views/proportional_image_view.h"
  60. #include "ui/message_center/views/relative_time_formatter.h"
  61. #include "ui/strings/grit/ui_strings.h"
  62. #include "ui/views/animation/animation_builder.h"
  63. #include "ui/views/background.h"
  64. #include "ui/views/controls/focus_ring.h"
  65. #include "ui/views/controls/label.h"
  66. #include "ui/views/controls/scroll_view.h"
  67. #include "ui/views/controls/textfield/textfield.h"
  68. #include "ui/views/layout/box_layout.h"
  69. #include "ui/views/layout/box_layout_view.h"
  70. #include "ui/views/layout/flex_layout.h"
  71. #include "ui/views/layout/flex_layout_types.h"
  72. #include "ui/views/layout/flex_layout_view.h"
  73. #include "ui/views/layout/layout_types.h"
  74. #include "ui/views/layout/table_layout.h"
  75. #include "ui/views/metadata/view_factory_internal.h"
  76. #include "ui/views/style/typography.h"
  77. #include "ui/views/view.h"
  78. #include "ui/views/view_class_properties.h"
  79. namespace {
  80. constexpr auto kNotificationViewPadding = gfx::Insets(4);
  81. constexpr int kMainRightViewVerticalSpacing = 4;
  82. // This padding is applied to all the children of `main_right_view_` except the
  83. // action buttons.
  84. constexpr auto kMainRightViewChildPadding = gfx::Insets::TLBR(0, 14, 0, 0);
  85. constexpr auto kImageContainerPadding = gfx::Insets::TLBR(12, 14, 12, 12);
  86. constexpr auto kActionButtonsRowPadding = gfx::Insets::TLBR(4, 38, 12, 4);
  87. constexpr int kActionsRowHorizontalSpacing = 8;
  88. constexpr auto kContentRowPadding = gfx::Insets::TLBR(16, 0, 0, 0);
  89. constexpr int kLeftContentVerticalSpacing = 4;
  90. constexpr int kTitleRowMinimumWidth = 186;
  91. constexpr int kTitleRowSpacing = 6;
  92. constexpr auto kHeaderRowExpandedPadding = gfx::Insets::TLBR(4, 0, 8, 0);
  93. constexpr auto kHeaderRowCollapsedPadding = gfx::Insets::TLBR(0, 0, 8, 0);
  94. constexpr auto kRightContentCollapsedPadding = gfx::Insets::TLBR(12, 0, 0, 16);
  95. constexpr auto kRightContentExpandedPadding = gfx::Insets::TLBR(20, 0, 0, 16);
  96. constexpr auto kTimeStampInCollapsedStatePadding =
  97. gfx::Insets::TLBR(0, 0, 0, 16);
  98. // Bullet character. The divider symbol between the title and the timestamp.
  99. constexpr char16_t kTitleRowDivider[] = u"\u2022";
  100. constexpr char kGoogleSansFont[] = "Google Sans";
  101. constexpr int kAppIconViewSize = 24;
  102. constexpr int kAppIconImageSize = 16;
  103. constexpr int kTitleCharacterLimit =
  104. message_center::kNotificationWidth * message_center::kMaxTitleLines /
  105. message_center::kMinPixelsPerTitleCharacter;
  106. constexpr int kTitleLabelSize = 13;
  107. constexpr int kTitleLabelExpandedMaxLines = 2;
  108. constexpr int kTitleLabelCollapsedMaxLines = 1;
  109. constexpr int kTimestampInCollapsedViewSize = 12;
  110. constexpr int kMessageLabelSize = 12;
  111. // The size for `icon_view_`, which is the icon within right content (between
  112. // title/message view and expand button).
  113. constexpr int kIconViewSize = 48;
  114. // Target contrast ratio to reach when adjusting colors in dark mode.
  115. constexpr float kDarkModeMinContrastRatio = 6.0;
  116. // If the image displayed in `icon_view()` is smaller in either width or height
  117. // than this value, we draw a background around the image.
  118. constexpr int kSmallImageBackgroundThreshold = 6;
  119. // Helpers ---------------------------------------------------------------------
  120. // Configure the style for labels in notification view. `is_color_primary`
  121. // indicates if the color of the text is primary or secondary text color.
  122. void ConfigureLabelStyle(
  123. views::Label* label,
  124. int size,
  125. bool is_color_primary,
  126. gfx::Font::Weight font_weight = gfx::Font::Weight::NORMAL) {
  127. label->SetAutoColorReadabilityEnabled(false);
  128. label->SetFontList(
  129. gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, size, font_weight));
  130. auto layer_type =
  131. is_color_primary
  132. ? ash::AshColorProvider::ContentLayerType::kTextColorPrimary
  133. : ash::AshColorProvider::ContentLayerType::kTextColorSecondary;
  134. label->SetEnabledColor(
  135. ash::AshColorProvider::Get()->GetContentLayerColor(layer_type));
  136. }
  137. // Create a view that will contain the `content_row`,
  138. // `message_label_in_expanded_state_`, inline settings and the large image.
  139. views::Builder<views::View> CreateMainRightViewBuilder() {
  140. auto layout_manager = std::make_unique<views::FlexLayout>();
  141. layout_manager
  142. ->SetDefault(views::kMarginsKey,
  143. gfx::Insets::TLBR(0, 0, kMainRightViewVerticalSpacing, 0))
  144. .SetOrientation(views::LayoutOrientation::kVertical);
  145. return views::Builder<views::View>()
  146. .SetID(message_center::NotificationViewBase::ViewId::kMainRightView)
  147. .SetLayoutManager(std::move(layout_manager))
  148. .SetProperty(
  149. views::kFlexBehaviorKey,
  150. views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
  151. views::MaximumFlexSizeRule::kUnbounded));
  152. }
  153. // Create a view containing the title and message for the notification in a
  154. // single line. This is used when a grouped child notification is in a
  155. // collapsed parent notification.
  156. views::Builder<views::BoxLayoutView> CreateCollapsedSummaryBuilder(
  157. const message_center::Notification& notification) {
  158. return views::Builder<views::BoxLayoutView>()
  159. .SetID(
  160. message_center::NotificationViewBase::ViewId::kCollapsedSummaryView)
  161. .SetInsideBorderInsets(ash::kGroupedCollapsedSummaryInsets)
  162. .SetBetweenChildSpacing(ash::kGroupedCollapsedSummaryLabelSpacing)
  163. .SetOrientation(views::BoxLayout::Orientation::kHorizontal)
  164. .SetVisible(false)
  165. .AddChild(views::Builder<views::Label>()
  166. .SetText(notification.title())
  167. .SetFontList(gfx::FontList(
  168. {kGoogleSansFont}, gfx::Font::NORMAL, kTitleLabelSize,
  169. gfx::Font::Weight::MEDIUM)))
  170. .AddChild(views::Builder<views::Label>()
  171. .SetText(notification.message())
  172. .SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT)
  173. .SetTextStyle(views::style::STYLE_SECONDARY)
  174. .SetFontList(gfx::FontList(
  175. {kGoogleSansFont}, gfx::Font::NORMAL, kMessageLabelSize,
  176. gfx::Font::Weight::MEDIUM)));
  177. }
  178. views::Builder<ash::AshNotificationView::GroupedNotificationsContainer>
  179. CreateGroupedNotificationsContainerBuilder(
  180. ash::AshNotificationView* parent_notification_view) {
  181. return views::Builder<
  182. ash::AshNotificationView::GroupedNotificationsContainer>()
  183. .SetParentNotificationView(parent_notification_view)
  184. .SetOrientation(views::BoxLayout::Orientation::kVertical);
  185. }
  186. // Perform a scale and translate animation by scale from (scale_value_x,
  187. // scalue_value_y) and translate from (translate_value_x, translate_value_y) to
  188. // its correct scale and position.
  189. void ScaleAndTranslateView(views::View* view,
  190. SkScalar scale_value_x,
  191. SkScalar scale_value_y,
  192. SkScalar translate_value_x,
  193. SkScalar translate_value_y,
  194. const std::string& animation_histogram_name) {
  195. gfx::Transform transform;
  196. transform.Translate(translate_value_x, translate_value_y);
  197. transform.Scale(scale_value_x, scale_value_y);
  198. ui::AnimationThroughputReporter reporter(
  199. view->layer()->GetAnimator(),
  200. ash::metrics_util::ForSmoothness(base::BindRepeating(
  201. [](const std::string& animation_histogram_name, int smoothness) {
  202. base::UmaHistogramPercentage(animation_histogram_name, smoothness);
  203. },
  204. animation_histogram_name)));
  205. views::AnimationBuilder()
  206. .SetPreemptionStrategy(
  207. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  208. .Once()
  209. .SetDuration(base::TimeDelta())
  210. .SetTransform(view, transform)
  211. .Then()
  212. .SetDuration(
  213. base::Milliseconds(ash::kLargeImageScaleAndTranslateDurationMs))
  214. .SetTransform(view, gfx::Transform(), gfx::Tween::ACCEL_0_100_DECEL_80);
  215. }
  216. } // namespace
  217. namespace ash {
  218. using CrossAxisAlignment = views::BoxLayout::CrossAxisAlignment;
  219. using MainAxisAlignment = views::BoxLayout::MainAxisAlignment;
  220. using Orientation = views::BoxLayout::Orientation;
  221. BEGIN_METADATA(AshNotificationView, NotificationTitleRow, views::View)
  222. END_METADATA
  223. void AshNotificationView::AddedToWidget() {
  224. // crbug/1337661: We need to abort animations in a grouped parent view when
  225. // it's widget is being destroyed. By default when a widget is destroyed, all
  226. // current animations are forced to finish. The grouped notification removal
  227. // animation triggers an additional resize animation when it is finished. This
  228. // needs to be aborted explicitly to prevent a crash. We do not need to this
  229. // observation for grouped notification views.
  230. if (!is_grouped_child_view_)
  231. widget_observation_.Observe(GetWidget());
  232. }
  233. void AshNotificationView::Layout() {
  234. if (is_animating_)
  235. return;
  236. message_center::NotificationViewBase::Layout();
  237. }
  238. void AshNotificationView::GroupedNotificationsContainer::
  239. ChildPreferredSizeChanged(views::View* view) {
  240. PreferredSizeChanged();
  241. parent_notification_view_->GroupedNotificationsPreferredSizeChanged();
  242. }
  243. void AshNotificationView::GroupedNotificationsContainer::
  244. SetParentNotificationView(AshNotificationView* parent_notification_view) {
  245. parent_notification_view_ = parent_notification_view;
  246. }
  247. AshNotificationView::NotificationTitleRow::NotificationTitleRow(
  248. const std::u16string& title)
  249. : title_view_(AddChildView(GenerateTitleView(title))),
  250. title_row_divider_(AddChildView(std::make_unique<views::Label>(
  251. kTitleRowDivider,
  252. views::style::CONTEXT_DIALOG_BODY_TEXT))),
  253. timestamp_in_collapsed_view_(
  254. AddChildView(std::make_unique<views::Label>())) {
  255. SetLayoutManager(std::make_unique<views::TableLayout>())
  256. ->AddColumn(views::LayoutAlignment::kStart,
  257. views::LayoutAlignment::kCenter, 1.0,
  258. views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
  259. .AddPaddingColumn(views::TableLayout::kFixedSize, kTitleRowSpacing)
  260. .AddColumn(views::LayoutAlignment::kStart,
  261. views::LayoutAlignment::kCenter,
  262. views::TableLayout::kFixedSize,
  263. views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
  264. .AddPaddingColumn(views::TableLayout::kFixedSize, kTitleRowSpacing)
  265. .AddColumn(views::LayoutAlignment::kStart,
  266. views::LayoutAlignment::kCenter, 100.0,
  267. views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
  268. .AddRows(1, views::TableLayout::kFixedSize);
  269. timestamp_in_collapsed_view_->SetProperty(views::kMarginsKey,
  270. kTimeStampInCollapsedStatePadding);
  271. timestamp_in_collapsed_view_->SetElideBehavior(gfx::ElideBehavior::NO_ELIDE);
  272. title_view_->SetProperty(
  273. views::kFlexBehaviorKey,
  274. views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
  275. views::MaximumFlexSizeRule::kPreferred));
  276. title_view_->SetMultiLine(true);
  277. title_view_->SetAllowCharacterBreak(true);
  278. title_view_->SetMaxLines(kTitleLabelExpandedMaxLines);
  279. ConfigureLabelStyle(title_row_divider_, kTimestampInCollapsedViewSize,
  280. /*is_color_primary=*/false);
  281. message_center_utils::InitLayerForAnimations(title_row_divider_);
  282. ConfigureLabelStyle(timestamp_in_collapsed_view_,
  283. kTimestampInCollapsedViewSize,
  284. /*is_color_primary=*/false);
  285. message_center_utils::InitLayerForAnimations(timestamp_in_collapsed_view_);
  286. ConfigureLabelStyle(title_view_, kTitleLabelSize,
  287. /*is_color_primary=*/true, gfx::Font::Weight::MEDIUM);
  288. }
  289. AshNotificationView::NotificationTitleRow::~NotificationTitleRow() {
  290. timestamp_update_timer_.Stop();
  291. }
  292. void AshNotificationView::NotificationTitleRow::UpdateTitle(
  293. const std::u16string& title) {
  294. title_view_->SetText(title);
  295. }
  296. void AshNotificationView::NotificationTitleRow::UpdateTimestamp(
  297. base::Time timestamp) {
  298. std::u16string relative_time;
  299. base::TimeDelta next_update;
  300. message_center::GetRelativeTimeStringAndNextUpdateTime(
  301. timestamp - base::Time::Now(), &relative_time, &next_update);
  302. timestamp_ = timestamp;
  303. timestamp_in_collapsed_view_->SetText(relative_time);
  304. // Unretained is safe as the timer cancels the task on destruction.
  305. timestamp_update_timer_.Start(
  306. FROM_HERE, next_update,
  307. base::BindOnce(&NotificationTitleRow::UpdateTimestamp,
  308. base::Unretained(this), timestamp));
  309. }
  310. void AshNotificationView::NotificationTitleRow::UpdateVisibility(
  311. bool in_collapsed_mode) {
  312. timestamp_in_collapsed_view_->SetVisible(in_collapsed_mode);
  313. title_row_divider_->SetVisible(in_collapsed_mode);
  314. }
  315. void AshNotificationView::NotificationTitleRow::
  316. PerformExpandCollapseAnimation() {
  317. if (title_row_divider_->GetVisible()) {
  318. message_center_utils::FadeInView(
  319. title_row_divider_, kTitleRowTimestampFadeInAnimationDelayMs,
  320. kTitleRowTimestampFadeInAnimationDurationMs,
  321. gfx::Tween::ACCEL_20_DECEL_100,
  322. "Ash.NotificationView.TitleRowDivider.FadeIn.AnimationSmoothness");
  323. DCHECK(timestamp_in_collapsed_view_->GetVisible());
  324. message_center_utils::FadeInView(
  325. timestamp_in_collapsed_view_, kTitleRowTimestampFadeInAnimationDelayMs,
  326. kTitleRowTimestampFadeInAnimationDurationMs,
  327. gfx::Tween::ACCEL_20_DECEL_100,
  328. "Ash.NotificationView.TimestampInTitle.FadeIn.AnimationSmoothness");
  329. }
  330. }
  331. gfx::Size AshNotificationView::NotificationTitleRow::CalculatePreferredSize()
  332. const {
  333. // TODO(crbug.com/1349528): The size constraint is not passed down from the
  334. // views tree in the first round of layout, so setting a fixed width to bound
  335. // the view. The layout manager can size the view beyond this width if there
  336. // is available space. This works similar to applying a max width on the
  337. // internal labels.
  338. return gfx::Size(kTitleRowMinimumWidth,
  339. GetHeightForWidth(kTitleRowMinimumWidth));
  340. }
  341. void AshNotificationView::NotificationTitleRow::OnThemeChanged() {
  342. views::View::OnThemeChanged();
  343. title_view_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
  344. AshColorProvider::ContentLayerType::kTextColorPrimary));
  345. SkColor secondary_text_color = AshColorProvider::Get()->GetContentLayerColor(
  346. AshColorProvider::ContentLayerType::kTextColorSecondary);
  347. title_row_divider_->SetEnabledColor(secondary_text_color);
  348. timestamp_in_collapsed_view_->SetEnabledColor(secondary_text_color);
  349. }
  350. // static
  351. const char AshNotificationView::kViewClassName[] = "AshNotificationView";
  352. AshNotificationView::AshNotificationView(
  353. const message_center::Notification& notification,
  354. bool shown_in_popup)
  355. : NotificationViewBase(notification),
  356. is_grouped_parent_view_(notification.group_parent()),
  357. is_grouped_child_view_(notification.group_child()),
  358. shown_in_popup_(shown_in_popup) {
  359. message_center_observer_.Observe(message_center::MessageCenter::Get());
  360. // TODO(crbug/1232197): fix views and layout to match spec.
  361. // Instantiate view instances and define layout and view hierarchy.
  362. SetLayoutManager(std::make_unique<views::BoxLayout>(
  363. views::BoxLayout::Orientation::kVertical,
  364. notification.group_child() ? gfx::Insets() : kNotificationViewPadding));
  365. auto content_row_layout = std::make_unique<views::FlexLayout>();
  366. content_row_layout->SetInteriorMargin(kMainRightViewChildPadding);
  367. auto content_row_builder =
  368. CreateContentRowBuilder()
  369. .SetLayoutManager(std::move(content_row_layout))
  370. .AddChild(
  371. views::Builder<views::FlexLayoutView>()
  372. .SetID(kHeaderLeftContent)
  373. .SetOrientation(views::LayoutOrientation::kVertical)
  374. .SetInteriorMargin(kContentRowPadding)
  375. .SetProperty(views::kFlexBehaviorKey,
  376. views::FlexSpecification(
  377. views::MinimumFlexSizeRule::kScaleToZero,
  378. views::MaximumFlexSizeRule::kScaleToMaximum))
  379. .AddChild(
  380. CreateHeaderRowBuilder()
  381. .SetIsInAshNotificationView(true)
  382. .SetColor(
  383. AshColorProvider::Get()->GetContentLayerColor(
  384. AshColorProvider::ContentLayerType::
  385. kTextColorSecondary)))
  386. .AddChild(
  387. CreateLeftContentBuilder()
  388. .CopyAddressTo(&left_content_)
  389. .SetBetweenChildSpacing(kLeftContentVerticalSpacing)))
  390. .AddChild(
  391. views::Builder<views::FlexLayoutView>()
  392. .SetMainAxisAlignment(views::LayoutAlignment::kEnd)
  393. .SetProperty(views::kFlexBehaviorKey,
  394. views::FlexSpecification(
  395. views::MinimumFlexSizeRule::kPreferred,
  396. views::MaximumFlexSizeRule::kUnbounded))
  397. .AddChild(CreateRightContentBuilder().SetProperty(
  398. views::kCrossAxisAlignmentKey,
  399. views::LayoutAlignment::kStart))
  400. .AddChild(
  401. views::Builder<views::FlexLayoutView>()
  402. .SetOrientation(views::LayoutOrientation::kVertical)
  403. .AddChild(
  404. views::Builder<views::BoxLayoutView>()
  405. .SetMainAxisAlignment(MainAxisAlignment::kEnd)
  406. .SetMinimumCrossAxisSize(
  407. kControlButtonsContainerMinimumHeight)
  408. .AddChild(
  409. CreateControlButtonsBuilder()
  410. .CopyAddressTo(&control_buttons_view_)
  411. .SetProperty(
  412. views::kCrossAxisAlignmentKey,
  413. views::LayoutAlignment::kEnd)
  414. .SetButtonIconColors(
  415. AshColorProvider::Get()
  416. ->GetContentLayerColor(
  417. AshColorProvider::
  418. ContentLayerType::
  419. kIconColorPrimary))))
  420. .AddChild(
  421. views::Builder<AshNotificationExpandButton>()
  422. .CopyAddressTo(&expand_button_)
  423. .SetCallback(base::BindRepeating(
  424. &AshNotificationView::ToggleExpand,
  425. base::Unretained(this)))
  426. .SetProperty(views::kCrossAxisAlignmentKey,
  427. views::LayoutAlignment::kEnd))));
  428. // Main right view contains all the views besides control buttons, app icon,
  429. // grouped container and action buttons.
  430. auto main_right_view_builder =
  431. CreateMainRightViewBuilder()
  432. .CopyAddressTo(&main_right_view_)
  433. .AddChild(content_row_builder)
  434. .AddChild(
  435. views::Builder<views::Label>()
  436. .CopyAddressTo(&message_label_in_expanded_state_)
  437. .SetHorizontalAlignment(gfx::ALIGN_TO_HEAD)
  438. .SetMultiLine(true)
  439. .SetMaxLines(message_center::kMaxLinesForExpandedMessageLabel)
  440. .SetAllowCharacterBreak(true)
  441. .SetBorder(
  442. views::CreateEmptyBorder(kMainRightViewChildPadding))
  443. // TODO(crbug/682266): This is a workaround to that bug by
  444. // explicitly setting the width. Ideally, we should fix the
  445. // original bug, but it seems there's no obvious solution for
  446. // the bug according to https://crbug.com/678337#c7. We will
  447. // consider making changes to this code when the bug is fixed.
  448. .SetMaximumWidth(GetExpandedMessageLabelWidth()))
  449. .AddChild(CreateInlineSettingsBuilder())
  450. .AddChild(CreateImageContainerBuilder().SetProperty(
  451. views::kMarginsKey, kImageContainerPadding));
  452. ConfigureLabelStyle(message_label_in_expanded_state_, kMessageLabelSize,
  453. false);
  454. AddChildView(
  455. views::Builder<views::FlexLayoutView>()
  456. .CopyAddressTo(&main_view_)
  457. .SetOrientation(views::LayoutOrientation::kHorizontal)
  458. .AddChild(views::Builder<views::BoxLayoutView>()
  459. .SetID(kAppIconViewContainer)
  460. .SetOrientation(Orientation::kVertical)
  461. .SetMainAxisAlignment(MainAxisAlignment::kStart)
  462. .AddChild(views::Builder<RoundedImageView>()
  463. .CopyAddressTo(&app_icon_view_)
  464. .SetCornerRadius(kAppIconViewSize / 2)))
  465. .AddChild(main_right_view_builder)
  466. .Build());
  467. AddChildView(CreateCollapsedSummaryBuilder(notification)
  468. .CopyAddressTo(&collapsed_summary_view_)
  469. .Build());
  470. // We only need a scroll view if the notification is being shown in its own
  471. // popup. Scrolling is handled by `UnifiedMessageCenterView` otherwise. Having
  472. // a nested scroll view results in crbug/1302756.
  473. if (shown_in_popup) {
  474. AddChildView(
  475. views::Builder<views::ScrollView>()
  476. .CopyAddressTo(&grouped_notifications_scroll_view_)
  477. .SetBackgroundColor(absl::nullopt)
  478. .SetDrawOverflowIndicator(false)
  479. .ClipHeightTo(0, std::numeric_limits<int>::max())
  480. .SetContents(
  481. CreateGroupedNotificationsContainerBuilder(this).CopyAddressTo(
  482. &grouped_notifications_container_))
  483. .Build());
  484. static_cast<views::BoxLayout*>(GetLayoutManager())
  485. ->SetFlexForView(grouped_notifications_scroll_view_, 1);
  486. } else {
  487. AddChildView(CreateGroupedNotificationsContainerBuilder(this)
  488. .CopyAddressTo(&grouped_notifications_container_)
  489. .Build());
  490. }
  491. AddChildView(CreateActionsRow(std::make_unique<views::FlexLayout>()));
  492. // Custom layout and paddings for views in `AshNotificationView`.
  493. // Note that we also change the padding for some particular views in
  494. // UpdateViewForExpandedState().
  495. action_buttons_row()
  496. ->SetLayoutManager(std::make_unique<views::FlexLayout>())
  497. ->SetDefault(views::kMarginsKey,
  498. gfx::Insets::TLBR(0, 0, 0, kActionsRowHorizontalSpacing))
  499. .SetOrientation(views::LayoutOrientation::kHorizontal)
  500. .SetInteriorMargin(kActionButtonsRowPadding);
  501. action_buttons_row()->SetProperty(
  502. views::kFlexBehaviorKey,
  503. views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
  504. views::MaximumFlexSizeRule::kUnbounded));
  505. inline_reply()->SetProperty(
  506. views::kFlexBehaviorKey,
  507. views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
  508. views::MaximumFlexSizeRule::kUnbounded));
  509. static_cast<views::FlexLayout*>(header_row()->GetLayoutManager())
  510. ->SetDefault(views::kMarginsKey, gfx::Insets())
  511. .SetInteriorMargin(gfx::Insets());
  512. header_row()->ConfigureLabelsStyle(
  513. gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, kHeaderViewLabelSize,
  514. gfx::Font::Weight::NORMAL),
  515. gfx::Insets(), true);
  516. // Corner radius for popups is handled below. We do not set corner radius if
  517. // the view is in the message center here. Rounded corners for message_views
  518. // in the message center view are handled in `UnifiedMessageListView`.
  519. if (shown_in_popup_ && !notification.group_child()) {
  520. UpdateCornerRadius(kMessagePopupCornerRadius, kMessagePopupCornerRadius);
  521. layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
  522. layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
  523. layer()->SetRoundedCornerRadius(
  524. gfx::RoundedCornersF{kMessagePopupCornerRadius});
  525. layer()->SetIsFastRoundedCorner(true);
  526. }
  527. views::FocusRing::Get(this)->SetColorId(ui::kColorAshFocusRing);
  528. // Create layer in some views for animations.
  529. message_center_utils::InitLayerForAnimations(header_row());
  530. message_center_utils::InitLayerForAnimations(
  531. message_label_in_expanded_state_);
  532. message_center_utils::InitLayerForAnimations(actions_row());
  533. UpdateWithNotification(notification);
  534. }
  535. AshNotificationView::~AshNotificationView() = default;
  536. void AshNotificationView::SetGroupedChildExpanded(bool expanded) {
  537. collapsed_summary_view_->SetVisible(!expanded);
  538. main_view_->SetVisible(expanded);
  539. }
  540. void AshNotificationView::AnimateGroupedChildExpandedCollapse(bool expanded) {
  541. message_center_utils::InitLayerForAnimations(collapsed_summary_view_);
  542. message_center_utils::InitLayerForAnimations(main_view_);
  543. // Fade out `collapsed_summary_view_`, then fade in `main_view_` in expanded
  544. // state and vice versa in collapsed state.
  545. if (expanded) {
  546. message_center_utils::FadeOutView(
  547. collapsed_summary_view_,
  548. base::BindRepeating(
  549. [](base::WeakPtr<ash::AshNotificationView> parent,
  550. views::View* collapsed_summary_view) {
  551. if (parent) {
  552. collapsed_summary_view->layer()->SetOpacity(1.0f);
  553. collapsed_summary_view->SetVisible(false);
  554. }
  555. },
  556. weak_factory_.GetWeakPtr(), collapsed_summary_view_),
  557. 0, kCollapsedSummaryViewAnimationDurationMs, gfx::Tween::LINEAR,
  558. "Ash.NotificationView.CollapsedSummaryView.FadeOut."
  559. "AnimationSmoothness");
  560. message_center_utils::FadeInView(
  561. main_view_, kCollapsedSummaryViewAnimationDurationMs,
  562. kChildMainViewFadeInAnimationDurationMs, gfx::Tween::LINEAR,
  563. "Ash.NotificationView.MainView.FadeIn.AnimationSmoothness");
  564. return;
  565. }
  566. message_center_utils::FadeOutView(
  567. main_view_,
  568. base::BindRepeating(
  569. [](base::WeakPtr<ash::AshNotificationView> parent,
  570. views::View* main_view) {
  571. if (parent) {
  572. main_view->layer()->SetOpacity(1.0f);
  573. main_view->SetVisible(false);
  574. }
  575. },
  576. weak_factory_.GetWeakPtr(), main_view_),
  577. 0, kChildMainViewFadeOutAnimationDurationMs, gfx::Tween::LINEAR,
  578. "Ash.NotificationView.MainView.FadeOut.AnimationSmoothness");
  579. message_center_utils::FadeInView(
  580. collapsed_summary_view_, kChildMainViewFadeOutAnimationDurationMs,
  581. kCollapsedSummaryViewAnimationDurationMs, gfx::Tween::LINEAR,
  582. "Ash.NotificationView.CollapsedSummaryView.FadeIn.AnimationSmoothness");
  583. }
  584. void AshNotificationView::AnimateSingleToGroup(
  585. NotificationGroupingController* grouping_controller,
  586. const std::string& notification_id,
  587. std::string parent_id) {
  588. ash::message_center_utils::InitLayerForAnimations(left_content());
  589. ash::message_center_utils::InitLayerForAnimations(right_content());
  590. ash::message_center_utils::InitLayerForAnimations(
  591. message_label_in_expanded_state_);
  592. ash::message_center_utils::InitLayerForAnimations(image_container_view());
  593. ash::message_center_utils::InitLayerForAnimations(action_buttons_row());
  594. auto on_animation_ended = base::BindOnce(
  595. [](base::WeakPtr<ash::AshNotificationView> parent,
  596. views::View* left_content, views::View* right_content,
  597. views::View* message_label_in_expanded_state,
  598. views::View* image_container_view, views::View* action_buttons_row,
  599. AshNotificationExpandButton* expand_button,
  600. NotificationGroupingController* grouping_controller,
  601. const std::string& notification_id, std::string parent_id) {
  602. if (!parent)
  603. return;
  604. auto* parent_notification =
  605. message_center::MessageCenter::Get()->FindNotificationById(
  606. parent_id);
  607. if (!parent_notification)
  608. return;
  609. grouping_controller->ConvertFromSingleToGroupNotificationAfterAnimation(
  610. notification_id, parent_id, parent_notification);
  611. left_content->layer()->SetOpacity(1.0f);
  612. right_content->layer()->SetOpacity(1.0f);
  613. message_label_in_expanded_state->layer()->SetOpacity(1.0f);
  614. image_container_view->layer()->SetOpacity(1.0f);
  615. action_buttons_row->layer()->SetOpacity(1.0f);
  616. // After fade out single notification and set up a group one, perform
  617. // a fade in.
  618. parent->AnimateSingleToGroupFadeIn();
  619. expand_button->set_previous_bounds(expand_button->GetContentsBounds());
  620. parent->Layout();
  621. expand_button->AnimateSingleToGroupNotification();
  622. },
  623. weak_factory_.GetWeakPtr(), left_content_, right_content(),
  624. message_label_in_expanded_state_, image_container_view(),
  625. action_buttons_row(), expand_button_, grouping_controller,
  626. notification_id, parent_id);
  627. std::pair<base::OnceClosure, base::OnceClosure> split =
  628. base::SplitOnceCallback(std::move(on_animation_ended));
  629. ui::AnimationThroughputReporter reporter(
  630. left_content()->layer()->GetAnimator(),
  631. metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) {
  632. base::UmaHistogramPercentage(
  633. "Ash.NotificationView.ConvertSingleToGroup.FadeOut."
  634. "AnimationSmoothness",
  635. smoothness);
  636. })));
  637. views::AnimationBuilder()
  638. .SetPreemptionStrategy(
  639. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  640. .OnEnded(std::move(split.first))
  641. .OnAborted(std::move(split.second))
  642. .Once()
  643. .SetDuration(
  644. base::Milliseconds(kConvertFromSingleToGroupFadeOutDurationMs))
  645. .SetOpacity(left_content(), 0.0f, gfx::Tween::LINEAR)
  646. .SetOpacity(right_content(), 0.0f, gfx::Tween::LINEAR)
  647. .SetOpacity(message_label_in_expanded_state_, 0.0f, gfx::Tween::LINEAR)
  648. .SetOpacity(image_container_view(), 0.0f, gfx::Tween::LINEAR)
  649. .SetOpacity(action_buttons_row(), 0.0f, gfx::Tween::LINEAR);
  650. }
  651. void AshNotificationView::ToggleExpand() {
  652. SetManuallyExpandedOrCollapsed(true);
  653. // Here we need to check if `inline_reply()` is still valid since user
  654. // can click the expand button when the view is being destructed, which
  655. // invalidate `inline_reply()`.
  656. if (inline_reply() && inline_reply()->GetVisible()) {
  657. // If inline reply is visible, fade it out then set expanded.
  658. message_center_utils::FadeOutView(
  659. inline_reply(),
  660. base::BindOnce(
  661. [](base::WeakPtr<ash::AshNotificationView> parent,
  662. views::View* inline_reply) {
  663. if (parent) {
  664. inline_reply->layer()->SetOpacity(1.0f);
  665. }
  666. },
  667. weak_factory_.GetWeakPtr(), inline_reply()),
  668. /*delay_in_ms=*/0, kInlineReplyFadeOutAnimationDurationMs,
  669. gfx::Tween::LINEAR,
  670. "Ash.NotificationView.InlineReply.FadeOut.AnimationSmoothness");
  671. }
  672. SetExpanded(!IsExpanded());
  673. PerformExpandCollapseAnimation();
  674. // Log expand button click action.
  675. if (IsExpanded()) {
  676. is_grouped_parent_view_
  677. ? metrics_utils::LogExpandButtonClickAction(
  678. metrics_utils::ExpandButtonClickAction::EXPAND_GROUP)
  679. : metrics_utils::LogExpandButtonClickAction(
  680. metrics_utils::ExpandButtonClickAction::EXPAND_INDIVIDUAL);
  681. } else {
  682. is_grouped_parent_view_
  683. ? metrics_utils::LogExpandButtonClickAction(
  684. metrics_utils::ExpandButtonClickAction::COLLAPSE_GROUP)
  685. : metrics_utils::LogExpandButtonClickAction(
  686. metrics_utils::ExpandButtonClickAction::COLLAPSE_INDIVIDUAL);
  687. }
  688. }
  689. void AshNotificationView::GroupedNotificationsPreferredSizeChanged() {
  690. PreferredSizeChanged();
  691. }
  692. base::TimeDelta AshNotificationView::GetBoundsAnimationDuration(
  693. const message_center::Notification& notification) const {
  694. // This is called after the parent gets notified of
  695. // `ChildPreferredSizeChanged()`, so the current expanded state is the target
  696. // state.
  697. if (!notification.image().IsEmpty())
  698. return base::Milliseconds(kLargeImageExpandAndCollapseAnimationDuration);
  699. if (HasInlineReply(notification) || is_grouped_parent_view_) {
  700. if (IsExpanded()) {
  701. return base::Milliseconds(
  702. kInlineReplyAndGroupedParentExpandAnimationDuration);
  703. }
  704. return base::Milliseconds(
  705. kInlineReplyAndGroupedParentCollapseAnimationDuration);
  706. }
  707. if (inline_settings_row() && inline_settings_row()->GetVisible()) {
  708. return base::Milliseconds(
  709. kInlineSettingsExpandAndCollapseAnimationDuration);
  710. }
  711. if (IsExpanded())
  712. return base::Milliseconds(kGeneralExpandAnimationDuration);
  713. return base::Milliseconds(kGeneralCollapseAnimationDuration);
  714. }
  715. void AshNotificationView::AddGroupNotification(
  716. const message_center::Notification& notification) {
  717. DCHECK(is_grouped_parent_view_);
  718. // Do not add a grouped notification if a view for it already exists.
  719. if (FindGroupNotificationView(notification.id()))
  720. return;
  721. auto notification_view =
  722. std::make_unique<AshNotificationView>(notification,
  723. /*shown_in_popup=*/false);
  724. notification_view->SetGroupedChildExpanded(IsExpanded());
  725. notification_view->set_parent_message_view(this);
  726. notification_view->set_scroller(
  727. scroller() ? scroller() : grouped_notifications_scroll_view_);
  728. header_row()->SetTimestamp(notification.timestamp());
  729. grouped_notifications_container_->AddChildViewAt(std::move(notification_view),
  730. 0);
  731. total_grouped_notifications_++;
  732. left_content_->SetVisible(false);
  733. UpdateGroupedNotificationsVisibility();
  734. expand_button_->UpdateGroupedNotificationsCount(total_grouped_notifications_);
  735. PreferredSizeChanged();
  736. }
  737. void AshNotificationView::PopulateGroupNotifications(
  738. const std::vector<const message_center::Notification*>& notifications) {
  739. DCHECK(is_grouped_parent_view_);
  740. // Clear all grouped notifications since we will add all grouped notifications
  741. // from scratch.
  742. total_grouped_notifications_ = 0;
  743. grouped_notifications_container_->RemoveAllChildViews();
  744. for (auto* notification : notifications) {
  745. auto notification_view =
  746. std::make_unique<AshNotificationView>(*notification,
  747. /*shown_in_popup=*/false);
  748. if (!total_grouped_notifications_)
  749. header_row()->SetTimestamp(notification->timestamp());
  750. notification_view->SetVisible(
  751. total_grouped_notifications_ <
  752. message_center_style::kMaxGroupedNotificationsInCollapsedState ||
  753. IsExpanded());
  754. notification_view->SetGroupedChildExpanded(IsExpanded());
  755. notification_view->set_parent_message_view(this);
  756. notification_view->set_scroller(
  757. scroller() ? scroller() : grouped_notifications_scroll_view_);
  758. grouped_notifications_container_->AddChildView(
  759. std::move(notification_view));
  760. total_grouped_notifications_++;
  761. }
  762. left_content_->SetVisible(total_grouped_notifications_ == 0);
  763. expand_button_->UpdateGroupedNotificationsCount(total_grouped_notifications_);
  764. }
  765. void AshNotificationView::RemoveGroupNotification(
  766. const std::string& notification_id) {
  767. auto* child_view = FindGroupNotificationView(notification_id);
  768. if (!child_view)
  769. return;
  770. base::WeakPtr<AshNotificationView> to_be_removed =
  771. static_cast<AshNotificationView*>(child_view)->weak_factory_.GetWeakPtr();
  772. if (to_be_removed) {
  773. // Abort any previously queued animations, if a remove animation was in
  774. // progress this will cause `to_be_removed` to be deleted. Because of this
  775. // we need to use a weakptr to ensure we do not try to animate an already
  776. // deleted view.
  777. to_be_removed->layer()->GetAnimator()->AbortAllAnimations();
  778. }
  779. if (!to_be_removed)
  780. return;
  781. auto on_notification_slid_out = base::BindRepeating(
  782. [](base::WeakPtr<AshNotificationView> self,
  783. const std::string& notification_id) {
  784. if (!self)
  785. return;
  786. views::View* to_be_removed =
  787. self->FindGroupNotificationView(notification_id);
  788. if (!to_be_removed)
  789. return;
  790. self->total_grouped_notifications_--;
  791. self->expand_button_->UpdateGroupedNotificationsCount(
  792. self->total_grouped_notifications_);
  793. self->AnimateResizeAfterRemoval(to_be_removed);
  794. },
  795. weak_factory_.GetWeakPtr(), notification_id);
  796. auto on_animation_aborted = base::BindRepeating(
  797. [](base::WeakPtr<AshNotificationView> self,
  798. const std::string& notification_id) {
  799. if (!self)
  800. return;
  801. views::View* to_be_removed =
  802. self->FindGroupNotificationView(notification_id);
  803. if (!to_be_removed)
  804. return;
  805. self->total_grouped_notifications_--;
  806. self->expand_button_->UpdateGroupedNotificationsCount(
  807. self->total_grouped_notifications_);
  808. self->grouped_notifications_container_->RemoveChildViewT(to_be_removed);
  809. self->PreferredSizeChanged();
  810. },
  811. weak_factory_.GetWeakPtr(), notification_id);
  812. // If the removed notification has a layer transform it has already been slid
  813. // out (For example user swiped it by dragging). We only need to animate a
  814. // slide out if there is no transform.
  815. if (to_be_removed && to_be_removed->layer()->transform().IsIdentity()) {
  816. message_center_utils::SlideOutView(
  817. to_be_removed.get(), on_notification_slid_out, on_animation_aborted,
  818. /*delay_in_ms=*/0,
  819. /*duration_in_ms=*/kSlideOutGroupedNotificationAnimationDurationMs,
  820. gfx::Tween::LINEAR,
  821. "Ash.Notification.GroupNotification.SlideOut.AnimationSmoothness");
  822. } else {
  823. on_notification_slid_out.Run();
  824. }
  825. }
  826. const char* AshNotificationView::GetClassName() const {
  827. return kViewClassName;
  828. }
  829. void AshNotificationView::UpdateViewForExpandedState(bool expanded) {
  830. // Grouped parent views should always use the expanded paddings, even if they
  831. // are collapsed.
  832. bool use_expanded_padding = expanded || is_grouped_parent_view_;
  833. header_row()->SetVisible(is_grouped_parent_view_ || expanded);
  834. header_row()->SetTimestampVisible(!is_grouped_parent_view_ || !expanded);
  835. if (title_row_) {
  836. title_row_->UpdateVisibility(IsExpandable() && !expanded);
  837. title_row_->title_view()->SetMaxLines(
  838. expanded ? kTitleLabelExpandedMaxLines : kTitleLabelCollapsedMaxLines);
  839. }
  840. if (message_label()) {
  841. // `message_label()` is shown only in collapsed mode.
  842. message_label()->SetVisible(!expanded);
  843. message_label_in_expanded_state_->SetVisible(expanded &&
  844. !is_grouped_parent_view_);
  845. }
  846. // Custom padding for app icon and expand button. These 2 views should always
  847. // use the same padding value so that they are vertical aligned.
  848. app_icon_view_->SetProperty(views::kMarginsKey,
  849. use_expanded_padding ? kAppIconExpandedPadding
  850. : kAppIconCollapsedPadding);
  851. right_content()->SetProperty(
  852. views::kMarginsKey, use_expanded_padding ? kRightContentExpandedPadding
  853. : kRightContentCollapsedPadding);
  854. expand_button_->SetProperty(
  855. views::kMarginsKey, use_expanded_padding ? kExpandButtonExpandedPadding
  856. : kExpandButtonCollapsedPadding);
  857. header_row()->SetProperty(views::kMarginsKey,
  858. use_expanded_padding ? kHeaderRowExpandedPadding
  859. : kHeaderRowCollapsedPadding);
  860. expand_button_->SetExpanded(expanded);
  861. if (is_grouped_parent_view_) {
  862. if (grouped_notifications_scroll_view_) {
  863. grouped_notifications_scroll_view_->ClipHeightTo(
  864. 0, CalculateMaxHeightForGroupedNotifications());
  865. }
  866. auto* grouped_notifications_container_layout_manager =
  867. static_cast<views::BoxLayout*>(
  868. grouped_notifications_container_->GetLayoutManager());
  869. grouped_notifications_container_layout_manager->set_inside_border_insets(
  870. expanded ? kGroupedNotificationContainerExpandedInsets
  871. : kGroupedNotificationContainerCollapsedInsets);
  872. grouped_notifications_container_layout_manager->set_between_child_spacing(
  873. expanded ? kGroupedNotificationsExpandedSpacing
  874. : kGroupedNotificationsCollapsedSpacing);
  875. int notification_count = 0;
  876. for (auto* child : grouped_notifications_container_->children()) {
  877. auto* notification_view = static_cast<AshNotificationView*>(child);
  878. notification_view->AnimateGroupedChildExpandedCollapse(expanded);
  879. notification_view->SetGroupedChildExpanded(expanded);
  880. notification_count++;
  881. if (notification_count >
  882. message_center_style::kMaxGroupedNotificationsInCollapsedState) {
  883. notification_view->SetVisible(expanded);
  884. }
  885. }
  886. }
  887. NotificationViewBase::UpdateViewForExpandedState(expanded);
  888. message_label_in_expanded_state_->SetProperty(
  889. views::kMarginsKey,
  890. (actions_row()->GetVisible() || image_container_view()->GetVisible() ||
  891. is_grouped_child_view_
  892. ? kMessageLabelInExpandedStatePadding
  893. : kMessageLabelInExpandedStateExtendedPadding));
  894. }
  895. void AshNotificationView::UpdateWithNotification(
  896. const message_center::Notification& notification) {
  897. is_grouped_child_view_ = notification.group_child();
  898. is_grouped_parent_view_ = notification.group_parent();
  899. if (grouped_notifications_scroll_view_)
  900. grouped_notifications_scroll_view_->SetVisible(is_grouped_parent_view_);
  901. grouped_notifications_container_->SetVisible(is_grouped_parent_view_);
  902. if (is_grouped_child_view_ && !is_nested())
  903. SetIsNested();
  904. header_row()->SetIsInGroupChildNotification(is_grouped_child_view_);
  905. UpdateMessageLabelInExpandedState(notification);
  906. NotificationViewBase::UpdateWithNotification(notification);
  907. CreateOrUpdateSnoozeButton(notification);
  908. // Configure views style.
  909. UpdateIconAndButtonsColor(&notification);
  910. if (message_label())
  911. ConfigureLabelStyle(message_label(), kMessageLabelSize, false);
  912. if (inline_reply()) {
  913. SkColor text_color = ash::AshColorProvider::Get()->GetContentLayerColor(
  914. ash::AshColorProvider::ContentLayerType::kTextColorSecondary);
  915. inline_reply()->textfield()->SetTextColor(text_color);
  916. inline_reply()->textfield()->set_placeholder_text_color(text_color);
  917. }
  918. }
  919. void AshNotificationView::CreateOrUpdateHeaderView(
  920. const message_center::Notification& notification) {
  921. switch (notification.system_notification_warning_level()) {
  922. case message_center::SystemNotificationWarningLevel::WARNING:
  923. header_row()->SetSummaryText(
  924. l10n_util::GetStringUTF16(IDS_ASH_NOTIFICATION_WARNING_LABEL));
  925. break;
  926. case message_center::SystemNotificationWarningLevel::CRITICAL_WARNING:
  927. header_row()->SetSummaryText(l10n_util::GetStringUTF16(
  928. IDS_ASH_NOTIFICATION_CRITICAL_WARNING_LABEL));
  929. break;
  930. case message_center::SystemNotificationWarningLevel::NORMAL:
  931. header_row()->SetSummaryText(std::u16string());
  932. break;
  933. }
  934. NotificationViewBase::CreateOrUpdateHeaderView(notification);
  935. }
  936. void AshNotificationView::CreateOrUpdateTitleView(
  937. const message_center::Notification& notification) {
  938. if (notification.title().empty()) {
  939. if (title_row_) {
  940. DCHECK(left_content()->Contains(title_row_));
  941. left_content()->RemoveChildViewT(title_row_);
  942. title_row_ = nullptr;
  943. }
  944. return;
  945. }
  946. const std::u16string& title = gfx::TruncateString(
  947. notification.title(), kTitleCharacterLimit, gfx::WORD_BREAK);
  948. if (!title_row_) {
  949. title_row_ =
  950. AddViewToLeftContent(std::make_unique<NotificationTitleRow>(title));
  951. } else {
  952. title_row_->UpdateTitle(title);
  953. ReorderViewInLeftContent(title_row_);
  954. }
  955. title_row_->UpdateTimestamp(notification.timestamp());
  956. }
  957. void AshNotificationView::CreateOrUpdateSmallIconView(
  958. const message_center::Notification& notification) {
  959. if (is_grouped_child_view_ && !notification.icon().IsEmpty()) {
  960. app_icon_view_->SetImage(notification.icon().Rasterize(GetColorProvider()),
  961. gfx::Size(kAppIconViewSize, kAppIconViewSize));
  962. return;
  963. }
  964. UpdateAppIconView(&notification);
  965. }
  966. void AshNotificationView::CreateOrUpdateInlineSettingsViews(
  967. const message_center::Notification& notification) {
  968. if (inline_settings_enabled()) {
  969. DCHECK(message_center::SettingsButtonHandler::INLINE ==
  970. notification.rich_notification_data().settings_button_handler);
  971. return;
  972. }
  973. set_inline_settings_enabled(
  974. !is_grouped_child_view_ &&
  975. notification.rich_notification_data().settings_button_handler ==
  976. message_center::SettingsButtonHandler::INLINE);
  977. if (!inline_settings_enabled()) {
  978. return;
  979. }
  980. inline_settings_row()->SetLayoutManager(std::make_unique<views::BoxLayout>(
  981. views::BoxLayout::Orientation::kHorizontal));
  982. auto turn_off_notifications_button = GenerateNotificationLabelButton(
  983. base::BindRepeating(&AshNotificationView::DisableNotification,
  984. base::Unretained(this)),
  985. l10n_util::GetStringUTF16(
  986. IDS_ASH_NOTIFICATION_INLINE_SETTINGS_TURN_OFF_BUTTON_TEXT));
  987. turn_off_notifications_button_ = inline_settings_row()->AddChildView(
  988. std::move(turn_off_notifications_button));
  989. auto inline_settings_cancel_button = GenerateNotificationLabelButton(
  990. base::BindRepeating(&AshNotificationView::ToggleInlineSettings,
  991. base::Unretained(this)),
  992. l10n_util::GetStringUTF16(
  993. IDS_ASH_NOTIFICATION_INLINE_SETTINGS_CANCEL_BUTTON_TEXT));
  994. inline_settings_cancel_button_ = inline_settings_row()->AddChildView(
  995. std::move(inline_settings_cancel_button));
  996. }
  997. void AshNotificationView::CreateOrUpdateCompactTitleMessageView(
  998. const message_center::Notification& notification) {
  999. // No CompactTitleMessageView required. It is only used for progress
  1000. // notifications when the notification is collapsed, and Ash progress
  1001. // notifications only show a progress bar when collapsed.
  1002. }
  1003. void AshNotificationView::CreateOrUpdateProgressViews(
  1004. const message_center::Notification& notification) {
  1005. // AshNotificationView should have the status view, followed by the progress
  1006. // bar. This is the opposite of what is required of the chrome notification.
  1007. CreateOrUpdateProgressStatusView(notification);
  1008. CreateOrUpdateProgressBarView(notification);
  1009. }
  1010. void AshNotificationView::UpdateControlButtonsVisibility() {
  1011. NotificationViewBase::UpdateControlButtonsVisibility();
  1012. // Always hide snooze button in control buttons since we show this snooze
  1013. // button in actions button view.
  1014. control_buttons_view()->ShowSnoozeButton(false);
  1015. // Hide settings button for grouped child notifications.
  1016. if (is_grouped_child_view_)
  1017. control_buttons_view()->ShowSettingsButton(false);
  1018. }
  1019. bool AshNotificationView::IsIconViewShown() const {
  1020. return NotificationViewBase::IsIconViewShown() && !is_grouped_child_view_;
  1021. }
  1022. void AshNotificationView::SetExpandButtonEnabled(bool enabled) {
  1023. expand_button_->SetVisible(enabled);
  1024. }
  1025. bool AshNotificationView::IsExpandable() const {
  1026. // Inline settings can not be expanded.
  1027. if (GetMode() == Mode::SETTING)
  1028. return false;
  1029. // Notification should always be expandable since we hide `header_row()` in
  1030. // collapsed state.
  1031. return true;
  1032. }
  1033. void AshNotificationView::UpdateCornerRadius(int top_radius,
  1034. int bottom_radius) {
  1035. // Call parent's SetCornerRadius to update radius used for highlight path.
  1036. NotificationViewBase::SetCornerRadius(top_radius, bottom_radius);
  1037. UpdateBackground(top_radius, bottom_radius);
  1038. }
  1039. void AshNotificationView::SetDrawBackgroundAsActive(bool active) {}
  1040. void AshNotificationView::OnThemeChanged() {
  1041. views::View::OnThemeChanged();
  1042. UpdateBackground(top_radius_, bottom_radius_);
  1043. SkColor secondary_text_color = AshColorProvider::Get()->GetContentLayerColor(
  1044. AshColorProvider::ContentLayerType::kTextColorSecondary);
  1045. header_row()->SetColor(secondary_text_color);
  1046. if (message_label())
  1047. message_label()->SetEnabledColor(secondary_text_color);
  1048. if (control_buttons_view_) {
  1049. control_buttons_view_->SetButtonIconColors(
  1050. AshColorProvider::Get()->GetContentLayerColor(
  1051. AshColorProvider::ContentLayerType::kIconColorPrimary));
  1052. }
  1053. if (message_label_in_expanded_state_)
  1054. message_label_in_expanded_state_->SetEnabledColor(secondary_text_color);
  1055. UpdateIconAndButtonsColor(
  1056. message_center::MessageCenter::Get()->FindVisibleNotificationById(
  1057. notification_id()));
  1058. if (inline_reply()) {
  1059. SkColor text_color = ash::AshColorProvider::Get()->GetContentLayerColor(
  1060. ash::AshColorProvider::ContentLayerType::kTextColorSecondary);
  1061. inline_reply()->textfield()->SetTextColor(text_color);
  1062. inline_reply()->textfield()->set_placeholder_text_color(text_color);
  1063. }
  1064. if (icon_view() &&
  1065. (right_content()->width() - icon_view()->GetImageDrawingSize().width() >
  1066. kSmallImageBackgroundThreshold ||
  1067. right_content()->height() - icon_view()->GetImageDrawingSize().height() >
  1068. kSmallImageBackgroundThreshold)) {
  1069. icon_view()->set_apply_rounded_corners(false);
  1070. right_content()->SetBackground(views::CreateRoundedRectBackground(
  1071. ash::AshColorProvider::Get()->GetControlsLayerColor(
  1072. ash::AshColorProvider::ControlsLayerType::
  1073. kControlBackgroundColorInactive),
  1074. message_center::kImageCornerRadius));
  1075. }
  1076. }
  1077. std::unique_ptr<message_center::NotificationInputContainer>
  1078. AshNotificationView::GenerateNotificationInputContainer() {
  1079. return std::make_unique<AshNotificationInputContainer>(this);
  1080. }
  1081. std::unique_ptr<views::LabelButton>
  1082. AshNotificationView::GenerateNotificationLabelButton(
  1083. views::Button::PressedCallback callback,
  1084. const std::u16string& label) {
  1085. std::unique_ptr<views::LabelButton> actions_button =
  1086. std::make_unique<PillButton>(
  1087. std::move(callback), label, PillButton::Type::kIconlessAccentFloating,
  1088. /*icon=*/nullptr, kNotificationPillButtonHorizontalSpacing);
  1089. return actions_button;
  1090. }
  1091. gfx::Size AshNotificationView::GetIconViewSize() const {
  1092. return gfx::Size(kIconViewSize, kIconViewSize);
  1093. }
  1094. int AshNotificationView::GetLargeImageViewMaxWidth() const {
  1095. return message_center::kNotificationWidth - kNotificationViewPadding.width() -
  1096. kAppIconViewSize - kMainRightViewChildPadding.width();
  1097. }
  1098. void AshNotificationView::ToggleInlineSettings(const ui::Event& event) {
  1099. if (!inline_settings_enabled())
  1100. return;
  1101. bool should_show_inline_settings = !inline_settings_row()->GetVisible();
  1102. PerformToggleInlineSettingsAnimation(should_show_inline_settings);
  1103. NotificationViewBase::ToggleInlineSettings(event);
  1104. if (is_grouped_parent_view_) {
  1105. if (shown_in_popup_) {
  1106. grouped_notifications_scroll_view_->SetVisible(
  1107. !should_show_inline_settings);
  1108. } else {
  1109. grouped_notifications_container_->SetVisible(
  1110. !should_show_inline_settings);
  1111. }
  1112. } else {
  1113. // In settings UI, we only show the app icon and header row along with the
  1114. // inline settings UI.
  1115. header_row()->SetVisible(true);
  1116. left_content()->SetVisible(!should_show_inline_settings);
  1117. right_content()->SetVisible(!should_show_inline_settings);
  1118. }
  1119. expand_button_->SetVisible(!should_show_inline_settings);
  1120. PreferredSizeChanged();
  1121. }
  1122. void AshNotificationView::ActionButtonPressed(size_t index,
  1123. const ui::Event& event) {
  1124. NotificationViewBase::ActionButtonPressed(index, event);
  1125. // If inline reply is visible, fade out actions button and then fade in inline
  1126. // reply. Here we need to check if `inline_reply()` is still valid since user
  1127. // can click an action button when the view is being destructed, which
  1128. // invalidate `inline_reply()`.
  1129. if (inline_reply() && inline_reply()->GetVisible()) {
  1130. message_center_utils::InitLayerForAnimations(action_buttons_row());
  1131. message_center_utils::FadeOutView(
  1132. action_buttons_row(),
  1133. base::BindOnce(
  1134. [](base::WeakPtr<ash::AshNotificationView> parent,
  1135. views::View* action_buttons_row) {
  1136. if (parent) {
  1137. action_buttons_row->layer()->SetOpacity(1.0f);
  1138. action_buttons_row->SetVisible(false);
  1139. }
  1140. },
  1141. weak_factory_.GetWeakPtr(), action_buttons_row()),
  1142. /*delay_in_ms=*/0, kActionButtonsFadeOutAnimationDurationMs,
  1143. gfx::Tween::LINEAR,
  1144. "Ash.NotificationView.ActionButtonsRow.FadeOut.AnimationSmoothness");
  1145. // Delay for the action buttons to fade out, then fade in inline reply.
  1146. message_center_utils::InitLayerForAnimations(inline_reply());
  1147. message_center_utils::FadeInView(
  1148. inline_reply(), kActionButtonsFadeOutAnimationDurationMs,
  1149. kInlineReplyFadeInAnimationDurationMs, gfx::Tween::LINEAR,
  1150. "Ash.NotificationView.InlineReply.FadeIn.AnimationSmoothness");
  1151. }
  1152. }
  1153. views::View* AshNotificationView::FindGroupNotificationView(
  1154. const std::string& notification_id) {
  1155. auto id_match = [&notification_id](views::View* notification_view) {
  1156. return notification_id ==
  1157. static_cast<AshNotificationView*>(notification_view)
  1158. ->notification_id();
  1159. };
  1160. auto notification = std::find_if(
  1161. grouped_notifications_container_->children().begin(),
  1162. grouped_notifications_container_->children().end(), id_match);
  1163. return notification == grouped_notifications_container_->children().end()
  1164. ? nullptr
  1165. : *notification;
  1166. }
  1167. void AshNotificationView::OnNotificationRemoved(
  1168. const std::string& notification_id,
  1169. bool by_user) {
  1170. if (!is_grouped_parent_view_)
  1171. return;
  1172. RemoveGroupNotification(notification_id);
  1173. }
  1174. void AshNotificationView::OnWidgetClosing(views::Widget* widget) {
  1175. widget_observation_.Reset();
  1176. AbortAllAnimations();
  1177. }
  1178. void AshNotificationView::OnWidgetDestroying(views::Widget* widget) {
  1179. OnWidgetClosing(widget);
  1180. }
  1181. void AshNotificationView::AbortAllAnimations() {
  1182. std::vector<scoped_refptr<ui::LayerAnimator>> animators;
  1183. animators.push_back(layer()->GetAnimator());
  1184. for (auto* child_notification :
  1185. grouped_notifications_container_->children()) {
  1186. animators.push_back(child_notification->layer()->GetAnimator());
  1187. }
  1188. for (auto animator : animators) {
  1189. animator->AbortAllAnimations();
  1190. }
  1191. }
  1192. void AshNotificationView::CreateOrUpdateSnoozeButton(
  1193. const message_center::Notification& notification) {
  1194. if (!notification.should_show_snooze_button()) {
  1195. if (action_buttons_row()->Contains(snooze_button_)) {
  1196. action_buttons_row()->RemoveChildViewT(snooze_button_);
  1197. snooze_button_ = nullptr;
  1198. DCHECK(action_buttons_row()->Contains(snooze_button_spacer_));
  1199. action_buttons_row()->RemoveChildViewT(snooze_button_spacer_);
  1200. snooze_button_spacer_ = nullptr;
  1201. }
  1202. return;
  1203. }
  1204. if (snooze_button_) {
  1205. DCHECK(snooze_button_spacer_);
  1206. // Spacer and snooze button should be at the end of action buttons row.
  1207. action_buttons_row()->ReorderChildView(
  1208. snooze_button_spacer_, action_buttons_row()->children().size());
  1209. action_buttons_row()->ReorderChildView(
  1210. snooze_button_, action_buttons_row()->children().size());
  1211. return;
  1212. }
  1213. action_buttons_row()->AddChildView(
  1214. views::Builder<views::BoxLayoutView>()
  1215. .CopyAddressTo(&snooze_button_spacer_)
  1216. .SetMainAxisAlignment(MainAxisAlignment::kEnd)
  1217. .SetProperty(
  1218. views::kFlexBehaviorKey,
  1219. views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
  1220. views::MaximumFlexSizeRule::kUnbounded))
  1221. .Build());
  1222. auto snooze_button = std::make_unique<IconButton>(
  1223. base::BindRepeating(&AshNotificationView::OnSnoozeButtonPressed,
  1224. base::Unretained(this)),
  1225. IconButton::Type::kSmallFloating, &kNotificationSnoozeButtonIcon,
  1226. IDS_MESSAGE_CENTER_NOTIFICATION_SNOOZE_BUTTON_TOOLTIP);
  1227. snooze_button_ = action_buttons_row()->AddChildView(std::move(snooze_button));
  1228. }
  1229. void AshNotificationView::UpdateGroupedNotificationsVisibility() {
  1230. for (size_t i = 0; i < grouped_notifications_container_->children().size();
  1231. i++) {
  1232. auto* view = grouped_notifications_container_->children()[i];
  1233. bool show_notification_view =
  1234. IsExpanded() ||
  1235. i < message_center_style::kMaxGroupedNotificationsInCollapsedState;
  1236. if (view->GetVisible() == show_notification_view)
  1237. continue;
  1238. view->SetVisible(show_notification_view);
  1239. }
  1240. }
  1241. void AshNotificationView::UpdateMessageLabelInExpandedState(
  1242. const message_center::Notification& notification) {
  1243. if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS ||
  1244. notification.message().empty()) {
  1245. message_label_in_expanded_state_->SetVisible(false);
  1246. return;
  1247. }
  1248. message_label_in_expanded_state_->SetText(gfx::TruncateString(
  1249. notification.message(), message_center::kMessageCharacterLimit,
  1250. gfx::WORD_BREAK));
  1251. message_label_in_expanded_state_->SetVisible(true);
  1252. }
  1253. void AshNotificationView::UpdateBackground(int top_radius, int bottom_radius) {
  1254. SkColor background_color;
  1255. if (shown_in_popup_) {
  1256. background_color = AshColorProvider::Get()->GetBaseLayerColor(
  1257. AshColorProvider::BaseLayerType::kTransparent80);
  1258. } else {
  1259. background_color = AshColorProvider::Get()->GetControlsLayerColor(
  1260. AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive);
  1261. }
  1262. if (background_color == background_color_ && top_radius_ == top_radius &&
  1263. bottom_radius_ == bottom_radius) {
  1264. return;
  1265. }
  1266. if (!is_grouped_child_view_)
  1267. background_color_ = background_color;
  1268. top_radius_ = top_radius;
  1269. bottom_radius_ = bottom_radius;
  1270. SetBackground(views::CreateBackgroundFromPainter(
  1271. std::make_unique<message_center::NotificationBackgroundPainter>(
  1272. top_radius_, bottom_radius_, background_color_)));
  1273. }
  1274. int AshNotificationView::GetExpandedMessageLabelWidth() {
  1275. int notification_width = shown_in_popup_ ? message_center::kNotificationWidth
  1276. : kNotificationInMessageCenterWidth;
  1277. return notification_width - kNotificationViewPadding.width() -
  1278. kAppIconViewSize - kMainRightViewChildPadding.width() -
  1279. kMessageLabelInExpandedStatePadding.width();
  1280. }
  1281. void AshNotificationView::DisableNotification() {
  1282. message_center::MessageCenter::Get()->DisableNotification(notification_id());
  1283. }
  1284. void AshNotificationView::UpdateAppIconView(
  1285. const message_center::Notification* notification) {
  1286. // Grouped child notification use notification's icon for the app icon view,
  1287. // so we don't need further update here.
  1288. if (!notification ||
  1289. (is_grouped_child_view_ && !notification->icon().IsEmpty()))
  1290. return;
  1291. SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
  1292. AshColorProvider::ContentLayerType::kInvertedButtonLabelColor);
  1293. SkColor icon_background_color = CalculateIconAndButtonsColor(notification);
  1294. // TODO(crbug.com/768748): figure out if this has a performance impact and
  1295. // cache images if so.
  1296. gfx::Image masked_small_icon = notification->GenerateMaskedSmallIcon(
  1297. kAppIconImageSize, icon_color, icon_background_color, icon_color);
  1298. gfx::ImageSkia app_icon =
  1299. masked_small_icon.IsEmpty()
  1300. ? gfx::CreateVectorIcon(message_center::kProductIcon,
  1301. kAppIconImageSize, icon_color)
  1302. : masked_small_icon.AsImageSkia();
  1303. app_icon_view_->SetImage(
  1304. gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
  1305. kAppIconViewSize / 2, icon_background_color, app_icon));
  1306. }
  1307. SkColor AshNotificationView::CalculateIconAndButtonsColor(
  1308. const message_center::Notification* notification) {
  1309. SkColor default_color = AshColorProvider::Get()->GetControlsLayerColor(
  1310. AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
  1311. if (!notification)
  1312. return default_color;
  1313. auto color_id = notification->accent_color_id();
  1314. absl::optional<SkColor> accent_color = notification->accent_color();
  1315. if ((!color_id || !GetWidget()) && !accent_color.has_value())
  1316. return default_color;
  1317. SkColor fg_color;
  1318. // ColorProvider needs widget to be created.
  1319. if (color_id && GetWidget()) {
  1320. fg_color = GetColorProvider()->GetColor(color_id.value());
  1321. } else {
  1322. fg_color = accent_color.value();
  1323. }
  1324. // TODO(crbug/1351205): move color calculation logic to color mixer.
  1325. // TODO(crbug/1294459): re-evaluate contrast, maybe increase or use fixed HSL
  1326. float minContrastRatio =
  1327. DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
  1328. ? minContrastRatio = kDarkModeMinContrastRatio
  1329. : color_utils::kMinimumReadableContrastRatio;
  1330. // Actual color is kTransparent80, but BlendForMinContrast requires opaque.
  1331. SkColor bg_color = AshColorProvider::Get()->GetBaseLayerColor(
  1332. AshColorProvider::BaseLayerType::kOpaque);
  1333. return color_utils::BlendForMinContrast(
  1334. fg_color, bg_color,
  1335. /*high_contrast_foreground=*/absl::nullopt, minContrastRatio)
  1336. .color;
  1337. }
  1338. void AshNotificationView::UpdateIconAndButtonsColor(
  1339. const message_center::Notification* notification) {
  1340. UpdateAppIconView(notification);
  1341. SkColor icon_color = CalculateIconAndButtonsColor(notification);
  1342. SkColor button_color = icon_color;
  1343. bool use_default_button_color =
  1344. !notification ||
  1345. notification->rich_notification_data().ignore_accent_color_for_text;
  1346. if (use_default_button_color) {
  1347. button_color = AshColorProvider::Get()->GetControlsLayerColor(
  1348. AshColorProvider::ControlsLayerType::kControlBackgroundColorActive);
  1349. }
  1350. for (auto* action_button : action_buttons()) {
  1351. static_cast<PillButton*>(action_button)->SetButtonTextColor(button_color);
  1352. }
  1353. if (snooze_button_)
  1354. snooze_button_->SetIconColor(button_color);
  1355. }
  1356. void AshNotificationView::AnimateResizeAfterRemoval(
  1357. views::View* to_be_removed) {
  1358. auto on_resize_complete = base::BindRepeating(
  1359. [](base::WeakPtr<AshNotificationView> self) {
  1360. if (!self)
  1361. return;
  1362. self->set_is_animating(false);
  1363. if (self->shown_in_popup_) {
  1364. self->grouped_notifications_scroll_view_->Layout();
  1365. }
  1366. },
  1367. weak_factory_.GetWeakPtr());
  1368. int group_container_previous_height =
  1369. grouped_notifications_container_->height();
  1370. size_t removed_index =
  1371. grouped_notifications_container_->GetIndexOf(to_be_removed).value();
  1372. grouped_notifications_container_->RemoveChildViewT(to_be_removed).reset();
  1373. auto* notification_view_controller = message_center_utils::
  1374. GetActiveNotificationViewControllerForNotificationView(this);
  1375. if (notification_view_controller)
  1376. notification_view_controller->AnimateResize();
  1377. if (shown_in_popup_) {
  1378. grouped_notifications_scroll_view_->Layout();
  1379. } else {
  1380. Layout();
  1381. PreferredSizeChanged();
  1382. }
  1383. int grouped_container_height_reduction =
  1384. group_container_previous_height -
  1385. grouped_notifications_container_->height();
  1386. views::AnimationBuilder animation_builder;
  1387. if (grouped_notifications_container_->children().begin() + removed_index >=
  1388. grouped_notifications_container_->children().end()) {
  1389. return;
  1390. }
  1391. set_is_animating(true);
  1392. animation_builder.OnEnded(on_resize_complete);
  1393. for (auto it =
  1394. grouped_notifications_container_->children().begin() + removed_index;
  1395. it != grouped_notifications_container_->children().end(); it++) {
  1396. gfx::Rect child_bounds = (*it)->layer()->GetTargetBounds();
  1397. (*it)->layer()->SetBounds(gfx::Rect(
  1398. child_bounds.x(), child_bounds.y() + grouped_container_height_reduction,
  1399. child_bounds.width(), child_bounds.height()));
  1400. animation_builder.Once()
  1401. .SetDuration(base::Milliseconds(
  1402. message_center::kNotificationResizeAnimationDurationMs))
  1403. .SetBounds((*it), child_bounds, gfx::Tween::EASE_OUT);
  1404. }
  1405. }
  1406. void AshNotificationView::PerformExpandCollapseAnimation() {
  1407. if (title_row_)
  1408. title_row_->PerformExpandCollapseAnimation();
  1409. // Fade in `header row()` if this is not a grouped parent view.
  1410. if (header_row() && header_row()->GetVisible() && !is_grouped_parent_view_) {
  1411. message_center_utils::FadeInView(
  1412. header_row(), kHeaderRowFadeInAnimationDelayMs,
  1413. kHeaderRowFadeInAnimationDurationMs, gfx::Tween::LINEAR,
  1414. "Ash.NotificationView.HeaderRow.FadeIn.AnimationSmoothness");
  1415. }
  1416. // Fade in `message_label()`. We only do fade in for both message view in
  1417. // expanded and collapsed mode if there's a difference between them (a.k.a
  1418. // when `message_label()` is truncated).
  1419. if (message_label() && message_label()->GetVisible() &&
  1420. IsMessageLabelTruncated()) {
  1421. message_center_utils::InitLayerForAnimations(message_label());
  1422. message_center_utils::FadeInView(
  1423. message_label(), kMessageLabelFadeInAnimationDelayMs,
  1424. kMessageLabelFadeInAnimationDurationMs, gfx::Tween::LINEAR,
  1425. "Ash.NotificationView.MessageLabel.FadeIn.AnimationSmoothness");
  1426. }
  1427. // Fade in `message_label_in_expanded_state_`.
  1428. if (message_label_in_expanded_state_ &&
  1429. message_label_in_expanded_state_->GetVisible() && message_label() &&
  1430. IsMessageLabelTruncated()) {
  1431. message_center_utils::FadeInView(
  1432. message_label_in_expanded_state_,
  1433. kMessageLabelInExpandedStateFadeInAnimationDelayMs,
  1434. kMessageLabelInExpandedStateFadeInAnimationDurationMs,
  1435. gfx::Tween::LINEAR,
  1436. "Ash.NotificationView.ExpandedMessageLabel.FadeIn.AnimationSmoothness");
  1437. }
  1438. if (!image_container_view()->children().empty()) {
  1439. PerformLargeImageAnimation();
  1440. }
  1441. if (actions_row() && actions_row()->GetVisible()) {
  1442. message_center_utils::FadeInView(
  1443. actions_row(), kActionsRowFadeInAnimationDelayMs,
  1444. kActionsRowFadeInAnimationDurationMs, gfx::Tween::LINEAR,
  1445. "Ash.NotificationView.ActionsRow.FadeIn.AnimationSmoothness");
  1446. }
  1447. if (total_grouped_notifications_) {
  1448. // Ensure layout is up-to-date before animating expand button. This is used
  1449. // for its bounds animation.
  1450. if (needs_layout())
  1451. Layout();
  1452. DCHECK(!needs_layout());
  1453. expand_button_->AnimateExpandCollapse();
  1454. }
  1455. }
  1456. void AshNotificationView::PerformLargeImageAnimation() {
  1457. message_center_utils::InitLayerForAnimations(image_container_view());
  1458. message_center_utils::InitLayerForAnimations(icon_view());
  1459. auto icon_view_bounds = icon_view()->GetBoundsInScreen();
  1460. auto large_image_bounds = image_container_view()->GetBoundsInScreen();
  1461. if (IsExpanded()) {
  1462. // In expanded state, do a scale and translate from `icon_view()` to
  1463. // `image_container_view()`.
  1464. message_center_utils::InitLayerForAnimations(image_container_view());
  1465. ScaleAndTranslateView(image_container_view(),
  1466. static_cast<double>(icon_view()->width()) /
  1467. image_container_view()->width(),
  1468. static_cast<double>(icon_view()->height()) /
  1469. image_container_view()->height(),
  1470. icon_view_bounds.x() - large_image_bounds.x(),
  1471. icon_view_bounds.y() - large_image_bounds.y(),
  1472. "Ash.NotificationView.ImageContainerView."
  1473. "ScaleAndTranslate.AnimationSmoothness");
  1474. // If we use different images for `icon_view()` and `image_container_view()`
  1475. // (a.k.a hide_icon_on_expanded() is false), fade in
  1476. // `image_container_view()`.
  1477. if (!hide_icon_on_expanded()) {
  1478. message_center_utils::FadeInView(
  1479. image_container_view(), kLargeImageFadeInAnimationDelayMs,
  1480. kLargeImageFadeInAnimationDurationMs, gfx::Tween::LINEAR,
  1481. "Ash.NotificationView.ImageContainerView.FadeIn.AnimationSmoothness");
  1482. }
  1483. return;
  1484. }
  1485. if (hide_icon_on_expanded()) {
  1486. // In collapsed state, if we use a same image for `icon_view()` and
  1487. // `image_container_view()`, perform a scale and translate from
  1488. // `image_container_view()` to `icon_view()`.
  1489. ScaleAndTranslateView(
  1490. icon_view(),
  1491. static_cast<double>(image_container_view()->width()) /
  1492. icon_view()->width(),
  1493. static_cast<double>(image_container_view()->height()) /
  1494. icon_view()->height(),
  1495. large_image_bounds.x() - icon_view_bounds.x(),
  1496. large_image_bounds.y() - icon_view_bounds.y(),
  1497. "Ash.NotificationView.IconView.ScaleAndTranslate.AnimationSmoothness");
  1498. return;
  1499. }
  1500. // In collapsed state, if we use a different image for `icon_view()` and
  1501. // `image_container_view()`, fade out and scale down `image_container_view()`.
  1502. message_center_utils::FadeOutView(
  1503. image_container_view(),
  1504. base::BindRepeating(
  1505. [](base::WeakPtr<ash::AshNotificationView> parent,
  1506. views::View* image_container_view) {
  1507. if (parent) {
  1508. image_container_view->layer()->SetOpacity(1.0f);
  1509. //
  1510. image_container_view->layer()->SetTransform(gfx::Transform());
  1511. image_container_view->SetVisible(false);
  1512. }
  1513. },
  1514. weak_factory_.GetWeakPtr(), image_container_view()),
  1515. kLargeImageFadeOutAnimationDelayMs, kLargeImageFadeOutAnimationDurationMs,
  1516. gfx::Tween::ACCEL_20_DECEL_100,
  1517. "Ash.NotificationView.ImageContainerView.FadeOut.AnimationSmoothness");
  1518. gfx::Transform transform;
  1519. // Translate y further down so that it would not interfere with the currently
  1520. // shown `icon_view()`.
  1521. transform.Translate((icon_view_bounds.x() - large_image_bounds.x()),
  1522. (icon_view_bounds.y() - large_image_bounds.y() +
  1523. large_image_bounds.height()));
  1524. transform.Scale(static_cast<double>(icon_view()->width()) /
  1525. image_container_view()->width(),
  1526. static_cast<double>(icon_view()->height()) /
  1527. image_container_view()->height());
  1528. ui::AnimationThroughputReporter reporter(
  1529. image_container_view()->layer()->GetAnimator(),
  1530. ash::metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) {
  1531. base::UmaHistogramPercentage(
  1532. "Ash.NotificationView.ImageContainerView.ScaleDown."
  1533. "AnimationSmoothness",
  1534. smoothness);
  1535. })));
  1536. views::AnimationBuilder()
  1537. .SetPreemptionStrategy(
  1538. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  1539. .Once()
  1540. .At(base::TimeDelta())
  1541. .SetTransform(image_container_view(), gfx::Transform())
  1542. .Then()
  1543. .SetDuration(base::Milliseconds(kLargeImageScaleDownDurationMs))
  1544. .SetTransform(image_container_view(), transform,
  1545. gfx::Tween::ACCEL_20_DECEL_100);
  1546. }
  1547. void AshNotificationView::PerformToggleInlineSettingsAnimation(
  1548. bool should_show_inline_settings) {
  1549. if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
  1550. ui::ScopedAnimationDurationScaleMode::ZERO_DURATION)
  1551. return;
  1552. message_center_utils::InitLayerForAnimations(main_right_view_);
  1553. message_center_utils::InitLayerForAnimations(inline_settings_row());
  1554. // Fade out views.
  1555. if (should_show_inline_settings) {
  1556. // Fade out left_content if it's visible.
  1557. if (left_content_->GetVisible()) {
  1558. message_center_utils::InitLayerForAnimations(left_content());
  1559. message_center_utils::FadeOutView(
  1560. left_content(),
  1561. base::BindRepeating(
  1562. [](base::WeakPtr<ash::AshNotificationView> parent,
  1563. views::View* left_content) {
  1564. if (parent) {
  1565. left_content->layer()->SetOpacity(1.0f);
  1566. left_content->SetVisible(false);
  1567. }
  1568. },
  1569. weak_factory_.GetWeakPtr(), left_content()),
  1570. /*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs,
  1571. gfx::Tween::LINEAR,
  1572. "Ash.NotificationView.LeftContent.FadeOut.AnimationSmoothness");
  1573. }
  1574. message_center_utils::FadeOutView(
  1575. expand_button_,
  1576. base::BindRepeating(
  1577. [](base::WeakPtr<ash::AshNotificationView> parent,
  1578. views::View* expand_button) {
  1579. if (parent) {
  1580. expand_button->layer()->SetOpacity(1.0f);
  1581. expand_button->SetVisible(false);
  1582. }
  1583. },
  1584. weak_factory_.GetWeakPtr(), expand_button_),
  1585. /*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs,
  1586. gfx::Tween::LINEAR,
  1587. "Ash.NotificationView.ExpandButton.FadeOut.AnimationSmoothness");
  1588. // Fade out icon_view() if it exists.
  1589. if (icon_view()) {
  1590. message_center_utils::InitLayerForAnimations(icon_view());
  1591. message_center_utils::FadeOutView(
  1592. icon_view(),
  1593. base::BindRepeating(
  1594. [](base::WeakPtr<ash::AshNotificationView> parent,
  1595. views::View* icon_view) {
  1596. if (parent) {
  1597. icon_view->layer()->SetOpacity(1.0f);
  1598. icon_view->SetVisible(false);
  1599. }
  1600. },
  1601. weak_factory_.GetWeakPtr(), icon_view()),
  1602. /*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs,
  1603. gfx::Tween::LINEAR,
  1604. "Ash.NotificationView.IconView.FadeOut.AnimationSmoothness");
  1605. }
  1606. } else {
  1607. message_center_utils::FadeOutView(
  1608. inline_settings_row(),
  1609. base::BindRepeating(
  1610. [](base::WeakPtr<ash::AshNotificationView> parent,
  1611. views::View* inline_settings_row) {
  1612. if (parent) {
  1613. inline_settings_row->layer()->SetOpacity(1.0f);
  1614. inline_settings_row->SetVisible(false);
  1615. }
  1616. },
  1617. weak_factory_.GetWeakPtr(), inline_settings_row()),
  1618. /*delay_in_ms=*/0, kToggleInlineSettingsFadeOutDurationMs,
  1619. gfx::Tween::LINEAR,
  1620. "Ash.NotificationView.InlineSettingsRow.FadeOut.AnimationSmoothness");
  1621. }
  1622. // Fade in views.
  1623. message_center_utils::FadeInView(
  1624. main_right_view_, kToggleInlineSettingsFadeInDelayMs,
  1625. kToggleInlineSettingsFadeInDurationMs, gfx::Tween::LINEAR,
  1626. "Ash.NotificationView.MainRightView.FadeIn.AnimationSmoothness");
  1627. }
  1628. void AshNotificationView::AnimateSingleToGroupFadeIn() {
  1629. auto* fade_in_view = shown_in_popup_ ? grouped_notifications_scroll_view_
  1630. : grouped_notifications_container_;
  1631. message_center_utils::InitLayerForAnimations(fade_in_view);
  1632. message_center_utils::FadeInView(
  1633. fade_in_view, /*delay_in_ms=*/0,
  1634. kConvertFromSingleToGroupFadeInDurationMs, gfx::Tween::LINEAR,
  1635. "Ash.NotificationView.ConvertSingleToGroup.FadeIn.AnimationSmoothness");
  1636. }
  1637. int AshNotificationView::CalculateMaxHeightForGroupedNotifications() {
  1638. auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
  1639. const WorkAreaInsets* work_area =
  1640. WorkAreaInsets::ForWindow(shelf->GetWindow()->GetRootWindow());
  1641. const int bottom = shelf->IsHorizontalAlignment()
  1642. ? shelf->GetShelfBoundsInScreen().y()
  1643. : work_area->user_work_area_bounds().bottom();
  1644. const int free_space_height_above_anchor =
  1645. bottom - work_area->user_work_area_bounds().y();
  1646. const int vertical_margin = 2 * message_center::kMarginBetweenPopups +
  1647. kNotificationViewPadding.height();
  1648. return free_space_height_above_anchor - main_view_->bounds().height() -
  1649. vertical_margin;
  1650. }
  1651. bool AshNotificationView::IsMessageLabelTruncated() {
  1652. // True if the expanded label has more than one line.
  1653. if (message_label_in_expanded_state_->GetRequiredLines() > 1)
  1654. return true;
  1655. // Get the first row's width of `message_label_in_expanded_state_`'s text,
  1656. // which is also the text width of this label since it has one line. If text
  1657. // width is larger than `left_content()`'s width, which is the space dedicated
  1658. // to `message_label()`, the text is truncated.
  1659. int text_width =
  1660. message_label_in_expanded_state_
  1661. ->GetSubstringBounds(gfx::Range(
  1662. 0, message_label_in_expanded_state_->GetText().length()))
  1663. .front()
  1664. .width();
  1665. return text_width > left_content()->width();
  1666. }
  1667. } // namespace ash