app_list_toast_container_view.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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/app_list/views/app_list_toast_container_view.h"
  5. #include <memory>
  6. #include "ash/app_list/app_list_model_provider.h"
  7. #include "ash/app_list/app_list_util.h"
  8. #include "ash/app_list/app_list_view_delegate.h"
  9. #include "ash/app_list/views/app_list_a11y_announcer.h"
  10. #include "ash/app_list/views/app_list_keyboard_controller.h"
  11. #include "ash/app_list/views/app_list_nudge_controller.h"
  12. #include "ash/app_list/views/app_list_toast_view.h"
  13. #include "ash/app_list/views/apps_grid_context_menu.h"
  14. #include "ash/constants/ash_features.h"
  15. #include "ash/public/cpp/app_list/app_list_model_delegate.h"
  16. #include "ash/public/cpp/app_list/app_list_types.h"
  17. #include "ash/public/cpp/feature_discovery_duration_reporter.h"
  18. #include "ash/public/cpp/feature_discovery_metric_util.h"
  19. #include "ash/resources/vector_icons/vector_icons.h"
  20. #include "ash/strings/grit/ash_strings.h"
  21. #include "ui/base/l10n/l10n_util.h"
  22. #include "ui/compositor/layer.h"
  23. #include "ui/views/accessibility/view_accessibility.h"
  24. #include "ui/views/animation/animation_builder.h"
  25. #include "ui/views/controls/button/label_button.h"
  26. #include "ui/views/layout/flex_layout.h"
  27. #include "ui/views/layout/flex_layout_types.h"
  28. #include "ui/views/view_class_properties.h"
  29. namespace ash {
  30. namespace {
  31. const gfx::VectorIcon* GetToastIconForOrder(AppListSortOrder order) {
  32. switch (order) {
  33. case AppListSortOrder::kNameAlphabetical:
  34. case AppListSortOrder::kNameReverseAlphabetical:
  35. return &kSortAlphabeticalIcon;
  36. case AppListSortOrder::kColor:
  37. return &kSortColorIcon;
  38. case AppListSortOrder::kCustom:
  39. NOTREACHED();
  40. return nullptr;
  41. }
  42. }
  43. constexpr auto kReorderUndoInteriorMargin = gfx::Insets::TLBR(8, 16, 8, 8);
  44. } // namespace
  45. AppListToastContainerView::AppListToastContainerView(
  46. AppListNudgeController* nudge_controller,
  47. AppListKeyboardController* keyboard_controller,
  48. AppListA11yAnnouncer* a11y_announcer,
  49. AppListViewDelegate* view_delegate,
  50. Delegate* delegate,
  51. bool tablet_mode)
  52. : a11y_announcer_(a11y_announcer),
  53. tablet_mode_(tablet_mode),
  54. view_delegate_(view_delegate),
  55. delegate_(delegate),
  56. nudge_controller_(nudge_controller),
  57. keyboard_controller_(keyboard_controller),
  58. current_toast_(AppListToastType::kNone) {
  59. DCHECK(a11y_announcer_);
  60. DCHECK(keyboard_controller_);
  61. SetLayoutManager(std::make_unique<views::FlexLayout>())
  62. ->SetMainAxisAlignment(views::LayoutAlignment::kCenter)
  63. .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
  64. .SetOrientation(views::LayoutOrientation::kHorizontal)
  65. .SetDefault(
  66. views::kFlexBehaviorKey,
  67. views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
  68. views::MaximumFlexSizeRule::kPreferred));
  69. if (!tablet_mode_) {
  70. // `context_menu_` is only set in clamshell mode. The sort options in tablet
  71. // mode are handled in RootWindowController with ShelfContextMenuModel.
  72. context_menu_ = std::make_unique<AppsGridContextMenu>();
  73. set_context_menu_controller(context_menu_.get());
  74. }
  75. }
  76. AppListToastContainerView::~AppListToastContainerView() {
  77. toast_view_ = nullptr;
  78. }
  79. bool AppListToastContainerView::OnKeyPressed(const ui::KeyEvent& event) {
  80. if (!delegate_)
  81. return false;
  82. if (event.key_code() == ui::VKEY_UP)
  83. return keyboard_controller_->MoveFocusUpFromToast(focused_app_column_);
  84. if (event.key_code() == ui::VKEY_DOWN)
  85. return keyboard_controller_->MoveFocusDownFromToast(focused_app_column_);
  86. return false;
  87. }
  88. bool AppListToastContainerView::HandleFocus(int column) {
  89. // Only handle the focus if a button on the toast exists.
  90. views::LabelButton* toast_button = GetToastButton();
  91. if (toast_button) {
  92. focused_app_column_ = column;
  93. toast_button->RequestFocus();
  94. return true;
  95. }
  96. views::Button* close_button = GetCloseButton();
  97. if (close_button) {
  98. focused_app_column_ = column;
  99. close_button->RequestFocus();
  100. return true;
  101. }
  102. return false;
  103. }
  104. void AppListToastContainerView::DisableFocusForShowingActiveFolder(
  105. bool disabled) {
  106. if (auto* toast_button = GetToastButton())
  107. toast_button->SetEnabled(!disabled);
  108. if (auto* close_button = GetCloseButton())
  109. close_button->SetEnabled(!disabled);
  110. // Prevent items from being accessed by ChromeVox.
  111. SetViewIgnoredForAccessibility(this, disabled);
  112. }
  113. void AppListToastContainerView::MaybeUpdateReorderNudgeView() {
  114. // If the expect state in `nudge_controller_` is different from the one in
  115. // toast container, update the actual reorder nudge view in the toast
  116. // container.
  117. if (nudge_controller_->ShouldShowReorderNudge() &&
  118. current_toast_ != AppListToastType::kReorderNudge) {
  119. CreateReorderNudgeView();
  120. } else if (!nudge_controller_->ShouldShowReorderNudge() &&
  121. current_toast_ == AppListToastType::kReorderNudge) {
  122. RemoveReorderNudgeView();
  123. }
  124. }
  125. void AppListToastContainerView::CreateReorderNudgeView() {
  126. if (toast_view_)
  127. return;
  128. int subtitle_message_id =
  129. tablet_mode_
  130. ? IDS_ASH_LAUNCHER_APP_LIST_REORDER_NUDGE_TABLET_MODE_SUBTITLE
  131. : IDS_ASH_LAUNCHER_APP_LIST_REORDER_NUDGE_CLAMSHELL_MODE_SUBTITLE;
  132. AppListToastView::Builder toast_view_builder(
  133. l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_APP_LIST_REORDER_NUDGE_TITLE));
  134. if (features::IsLauncherDismissButtonsOnSortNudgeAndToastEnabled()) {
  135. toast_view_builder.SetButton(
  136. l10n_util::GetStringUTF16(
  137. IDS_ASH_LAUNCHER_APP_LIST_REORDER_NUDGE_DISMISS_BUTTON),
  138. base::BindRepeating(&AppListToastContainerView::FadeOutToastView,
  139. base::Unretained(this)));
  140. }
  141. FeatureDiscoveryDurationReporter* reporter =
  142. FeatureDiscoveryDurationReporter::GetInstance();
  143. reporter->MaybeActivateObservation(
  144. feature_discovery::TrackableFeature::kAppListReorderAfterEducationNudge);
  145. reporter->MaybeActivateObservation(
  146. feature_discovery::TrackableFeature::
  147. kAppListReorderAfterEducationNudgePerTabletMode);
  148. toast_view_ = AddChildView(
  149. toast_view_builder.SetStyleForTabletMode(tablet_mode_)
  150. .SetSubtitle(l10n_util::GetStringUTF16(subtitle_message_id))
  151. .SetThemingIcons(tablet_mode_ ? &kReorderNudgeDarkTabletIcon
  152. : &kReorderNudgeDarkClamshellIcon,
  153. tablet_mode_ ? &kReorderNudgeLightTabletIcon
  154. : &kReorderNudgeLightClamshellIcon)
  155. .SetIconBackground(true)
  156. .Build());
  157. current_toast_ = AppListToastType::kReorderNudge;
  158. }
  159. void AppListToastContainerView::RemoveReorderNudgeView() {
  160. // If the nudge is requested to be removed, it is likely that it won't be
  161. // shown to the user again. Therefore, the nudge child view is directly
  162. // removed instead of made invisible.
  163. if (current_toast_ == AppListToastType::kReorderNudge)
  164. RemoveCurrentView();
  165. }
  166. void AppListToastContainerView::RemoveCurrentView() {
  167. if (toast_view_)
  168. RemoveChildViewT(toast_view_);
  169. toast_view_ = nullptr;
  170. current_toast_ = AppListToastType::kNone;
  171. }
  172. void AppListToastContainerView::UpdateVisibilityState(VisibilityState state) {
  173. visibility_state_ = state;
  174. // Return early if the reorder nudge is not showing when the app list is
  175. // hiding.
  176. if (nudge_controller_->is_visible() &&
  177. nudge_controller_->current_nudge() !=
  178. AppListNudgeController::NudgeType::kReorderNudge) {
  179. return;
  180. }
  181. // Return early if the privacy notice should be showing.
  182. if (nudge_controller_->current_nudge() ==
  183. AppListNudgeController::NudgeType::kPrivacyNotice) {
  184. return;
  185. }
  186. AppListNudgeController::NudgeType new_nudge =
  187. nudge_controller_->ShouldShowReorderNudge()
  188. ? AppListNudgeController::NudgeType::kReorderNudge
  189. : AppListNudgeController::NudgeType::kNone;
  190. // Update the visible and active state in `nudge_controller_`.
  191. switch (state) {
  192. case VisibilityState::kShown:
  193. nudge_controller_->SetNudgeVisible(true, new_nudge);
  194. break;
  195. case VisibilityState::kShownInBackground:
  196. // The nudge must be visible to change to inactive state.
  197. if (!nudge_controller_->is_visible())
  198. nudge_controller_->SetNudgeVisible(true, new_nudge);
  199. nudge_controller_->SetNudgeActive(false, new_nudge);
  200. break;
  201. case VisibilityState::kHidden:
  202. nudge_controller_->SetNudgeVisible(false, new_nudge);
  203. break;
  204. }
  205. }
  206. void AppListToastContainerView::OnTemporarySortOrderChanged(
  207. const absl::optional<AppListSortOrder>& new_order) {
  208. // Remove `toast_view_` when the temporary sorting order is cleared.
  209. if (!GetVisibilityForSortOrder(new_order)) {
  210. if (committing_sort_order_) {
  211. // When the toast view is closed due to committing the sort order via the
  212. // close button , the toast view should be faded out with animation.
  213. FadeOutToastView();
  214. } else {
  215. RemoveCurrentView();
  216. }
  217. return;
  218. }
  219. // The nudge view should be removed when the user triggers apps reordering.
  220. RemoveReorderNudgeView();
  221. const std::u16string toast_text = CalculateToastTextFromOrder(*new_order);
  222. const gfx::VectorIcon* toast_icon = GetToastIconForOrder(*new_order);
  223. const std::u16string a11y_text_on_undo_button =
  224. GetA11yTextOnUndoButtonFromOrder(*new_order);
  225. if (toast_view_) {
  226. // If the reorder undo toast is showing, updates the title and icon of the
  227. // toast.
  228. toast_view_->SetTitle(toast_text);
  229. toast_view_->SetIcon(toast_icon);
  230. toast_view_->toast_button()->GetViewAccessibility().OverrideName(
  231. a11y_text_on_undo_button);
  232. return;
  233. }
  234. AppListToastView::Builder toast_view_builder(toast_text);
  235. if (features::IsLauncherDismissButtonsOnSortNudgeAndToastEnabled()) {
  236. toast_view_builder.SetCloseButton(base::BindRepeating(
  237. &AppListToastContainerView::OnReorderCloseButtonClicked,
  238. base::Unretained(this)));
  239. }
  240. toast_view_ = AddChildView(
  241. toast_view_builder.SetStyleForTabletMode(tablet_mode_)
  242. .SetIcon(toast_icon)
  243. .SetButton(l10n_util::GetStringUTF16(
  244. IDS_ASH_LAUNCHER_UNDO_SORT_TOAST_ACTION_BUTTON),
  245. base::BindRepeating(
  246. &AppListToastContainerView::OnReorderUndoButtonClicked,
  247. base::Unretained(this)))
  248. .SetViewDelegate(view_delegate_)
  249. .Build());
  250. toast_view_->toast_button()->GetViewAccessibility().OverrideName(
  251. a11y_text_on_undo_button);
  252. toast_view_->UpdateInteriorMargins(kReorderUndoInteriorMargin);
  253. current_toast_ = AppListToastType::kReorderUndo;
  254. }
  255. bool AppListToastContainerView::GetVisibilityForSortOrder(
  256. const absl::optional<AppListSortOrder>& new_order) const {
  257. return new_order && *new_order != AppListSortOrder::kCustom;
  258. }
  259. void AppListToastContainerView::AnnounceSortOrder(AppListSortOrder new_order) {
  260. a11y_announcer_->Announce(CalculateToastTextFromOrder(new_order));
  261. }
  262. void AppListToastContainerView::AnnounceUndoSort() {
  263. a11y_announcer_->Announce(
  264. l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_UNDO_SORT_DONE_SPOKEN_TEXT));
  265. }
  266. views::LabelButton* AppListToastContainerView::GetToastButton() {
  267. if (!toast_view_)
  268. return nullptr;
  269. return toast_view_->toast_button();
  270. }
  271. views::Button* AppListToastContainerView::GetCloseButton() {
  272. if (!toast_view_)
  273. return nullptr;
  274. return toast_view_->close_button();
  275. }
  276. void AppListToastContainerView::OnReorderUndoButtonClicked() {
  277. AppListModelProvider::Get()->model()->delegate()->RequestAppListSortRevert();
  278. }
  279. void AppListToastContainerView::OnReorderCloseButtonClicked() {
  280. base::AutoReset auto_reset(&committing_sort_order_, true);
  281. view_delegate_->CommitTemporarySortOrder();
  282. }
  283. bool AppListToastContainerView::IsToastVisible() const {
  284. return toast_view_ && !(toast_view_->layer() &&
  285. toast_view_->layer()->GetTargetOpacity() == 0.0f);
  286. }
  287. void AppListToastContainerView::FadeOutToastView() {
  288. if (!toast_view_->layer()) {
  289. toast_view_->SetPaintToLayer();
  290. toast_view_->layer()->SetFillsBoundsOpaquely(false);
  291. }
  292. views::AnimationBuilder()
  293. .SetPreemptionStrategy(
  294. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  295. .OnEnded(
  296. base::BindOnce(&AppListToastContainerView::OnFadeOutToastViewComplete,
  297. weak_factory_.GetWeakPtr()))
  298. .OnAborted(
  299. base::BindOnce(&AppListToastContainerView::OnFadeOutToastViewComplete,
  300. weak_factory_.GetWeakPtr()))
  301. .Once()
  302. .SetDuration(base::Milliseconds(200))
  303. .SetOpacity(toast_view_->layer(), 0.0f, gfx::Tween::LINEAR);
  304. }
  305. void AppListToastContainerView::OnFadeOutToastViewComplete() {
  306. if (current_toast_ == AppListToastType::kReorderNudge)
  307. nudge_controller_->OnReorderNudgeConfirmed();
  308. RemoveCurrentView();
  309. delegate_->OnNudgeRemoved();
  310. }
  311. std::u16string AppListToastContainerView::CalculateToastTextFromOrder(
  312. AppListSortOrder order) const {
  313. switch (order) {
  314. case AppListSortOrder::kNameAlphabetical:
  315. case AppListSortOrder::kNameReverseAlphabetical:
  316. return l10n_util::GetStringUTF16(
  317. IDS_ASH_LAUNCHER_UNDO_SORT_TOAST_FOR_NAME_SORT);
  318. case AppListSortOrder::kColor:
  319. return l10n_util::GetStringUTF16(
  320. IDS_ASH_LAUNCHER_UNDO_SORT_TOAST_FOR_COLOR_SORT);
  321. case AppListSortOrder::kCustom:
  322. NOTREACHED();
  323. return u"";
  324. }
  325. }
  326. std::u16string AppListToastContainerView::GetA11yTextOnUndoButtonFromOrder(
  327. AppListSortOrder order) const {
  328. switch (order) {
  329. case AppListSortOrder::kNameAlphabetical:
  330. case AppListSortOrder::kNameReverseAlphabetical:
  331. return l10n_util::GetStringUTF16(
  332. IDS_ASH_LAUNCHER_UNDO_NAME_SORT_TOAST_SPOKEN_TEXT);
  333. case AppListSortOrder::kColor:
  334. return l10n_util::GetStringUTF16(
  335. IDS_ASH_LAUNCHER_UNDO_COLOR_SORT_TOAST_SPOKEN_TEXT);
  336. case AppListSortOrder::kCustom:
  337. NOTREACHED();
  338. return u"";
  339. }
  340. }
  341. } // namespace ash