From 971477fd1f1134e1d2288a0850a59d6a14820353 Mon Sep 17 00:00:00 2001 From: Curt Clemens Date: Fri, 17 Mar 2023 00:15:07 +0000 Subject: [PATCH] [Eche] Add new views for recent apps loading state This CL introduces new views for the recent apps view to represent a loading state. A follow-up CL will include the logic for displaying the loading view, error view, and transition to the normal recent apps buttons view. Screen capture showing the animation: https://drive.google.com/file/d/1uEOnaXQUT7SY2aug-e663vu-yUeqPgsR/view?usp=sharing&resourcekey=0-bxDJXQsjU0IS7W3B61cqbg Test: Manually verified that when setting loading_view_ to visible it will show. Verified that the non-loading view and buttons still work as intended. Bug: b/271478560 Change-Id: Ifb1d1cb8830dadbd5164a774a9eaa116cb024754 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4307837 Reviewed-by: Jon Mann Commit-Queue: Curt Clemens Reviewed-by: Pu Shi Cr-Commit-Position: refs/heads/main@{#1118447} --- ash/BUILD.gn | 8 +- .../phonehub/phone_hub_app_count_icon.cc | 19 ++- .../phonehub/phone_hub_app_count_icon.h | 6 +- ...mall_app_icon.cc => phone_hub_app_icon.cc} | 19 +-- ash/system/phonehub/phone_hub_app_icon.h | 37 ++++ ..._icon.cc => phone_hub_app_loading_icon.cc} | 20 ++- .../phonehub/phone_hub_app_loading_icon.h | 20 +++ .../phonehub/phone_hub_more_apps_button.cc | 37 ++-- .../phonehub/phone_hub_more_apps_button.h | 12 +- .../phonehub/phone_hub_recent_apps_view.cc | 161 ++++++++++++++---- .../phonehub/phone_hub_recent_apps_view.h | 21 +++ .../phonehub/phone_hub_small_app_icon.h | 25 --- .../phone_hub_small_app_loading_icon.h | 18 -- 13 files changed, 280 insertions(+), 123 deletions(-) rename ash/system/phonehub/{phone_hub_small_app_icon.cc => phone_hub_app_icon.cc} (52%) create mode 100644 ash/system/phonehub/phone_hub_app_icon.h rename ash/system/phonehub/{phone_hub_small_app_loading_icon.cc => phone_hub_app_loading_icon.cc} (73%) create mode 100644 ash/system/phonehub/phone_hub_app_loading_icon.h delete mode 100644 ash/system/phonehub/phone_hub_small_app_icon.h delete mode 100644 ash/system/phonehub/phone_hub_small_app_loading_icon.h diff --git a/ash/BUILD.gn b/ash/BUILD.gn index 31be257a83b08..310d0785e020c 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn @@ -1674,6 +1674,10 @@ component("ash") { "system/phonehub/phone_disconnected_view.h", "system/phonehub/phone_hub_app_count_icon.cc", "system/phonehub/phone_hub_app_count_icon.h", + "system/phonehub/phone_hub_app_icon.cc", + "system/phonehub/phone_hub_app_icon.h", + "system/phonehub/phone_hub_app_loading_icon.cc", + "system/phonehub/phone_hub_app_loading_icon.h", "system/phonehub/phone_hub_content_view.cc", "system/phonehub/phone_hub_content_view.h", "system/phonehub/phone_hub_interstitial_view.cc", @@ -1692,10 +1696,6 @@ component("ash") { "system/phonehub/phone_hub_recent_app_button.h", "system/phonehub/phone_hub_recent_apps_view.cc", "system/phonehub/phone_hub_recent_apps_view.h", - "system/phonehub/phone_hub_small_app_icon.cc", - "system/phonehub/phone_hub_small_app_icon.h", - "system/phonehub/phone_hub_small_app_loading_icon.cc", - "system/phonehub/phone_hub_small_app_loading_icon.h", "system/phonehub/phone_hub_tray.cc", "system/phonehub/phone_hub_tray.h", "system/phonehub/phone_hub_ui_controller.cc", diff --git a/ash/system/phonehub/phone_hub_app_count_icon.cc b/ash/system/phonehub/phone_hub_app_count_icon.cc index 85c9d210479ed..2c46c0bc64ef6 100644 --- a/ash/system/phonehub/phone_hub_app_count_icon.cc +++ b/ash/system/phonehub/phone_hub_app_count_icon.cc @@ -14,10 +14,13 @@ namespace ash { +namespace { + class NumberIconImageSource : public gfx::CanvasImageSource { public: - explicit NumberIconImageSource(size_t count) - : CanvasImageSource(gfx::Size(18, 18)), count_(count) {} + explicit NumberIconImageSource(size_t count, int size) + : CanvasImageSource(AppIcon::GetRecommendedImageSize(size)), + count_(count) {} NumberIconImageSource(const NumberIconImageSource&) = delete; NumberIconImageSource& operator=(const NumberIconImageSource&) = delete; @@ -49,8 +52,12 @@ class NumberIconImageSource : public gfx::CanvasImageSource { } }; +} // namespace + AppCountIcon::AppCountIcon(const int count) - : SmallAppIcon(gfx::Image( - gfx::CanvasImageSource::MakeImageSkia( - count))) {} -} // namespace ash \ No newline at end of file + : AppIcon(gfx::Image( + gfx::CanvasImageSource::MakeImageSkia( + count, + AppIcon::kSizeSmall)), + AppIcon::kSizeSmall) {} +} // namespace ash diff --git a/ash/system/phonehub/phone_hub_app_count_icon.h b/ash/system/phonehub/phone_hub_app_count_icon.h index 20304088d2d8b..0b9d3987be423 100644 --- a/ash/system/phonehub/phone_hub_app_count_icon.h +++ b/ash/system/phonehub/phone_hub_app_count_icon.h @@ -5,14 +5,14 @@ #ifndef ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_COUNT_ICON_H_ #define ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_COUNT_ICON_H_ #include "ash/ash_export.h" -#include "ash/system/phonehub/phone_hub_small_app_icon.h" +#include "ash/system/phonehub/phone_hub_app_icon.h" namespace ash { -class ASH_EXPORT AppCountIcon : public SmallAppIcon { +class ASH_EXPORT AppCountIcon : public AppIcon { public: explicit AppCountIcon(const int count); }; } // namespace ash -#endif // ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_COUNT_ICON_H_ \ No newline at end of file +#endif // ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_COUNT_ICON_H_ diff --git a/ash/system/phonehub/phone_hub_small_app_icon.cc b/ash/system/phonehub/phone_hub_app_icon.cc similarity index 52% rename from ash/system/phonehub/phone_hub_small_app_icon.cc rename to ash/system/phonehub/phone_hub_app_icon.cc index 2aa427dfe9a93..5abbdba8d93d2 100644 --- a/ash/system/phonehub/phone_hub_small_app_icon.cc +++ b/ash/system/phonehub/phone_hub_app_icon.cc @@ -2,28 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ash/system/phonehub/phone_hub_small_app_icon.h" +#include "ash/system/phonehub/phone_hub_app_icon.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_operations.h" -namespace { - -// Appearance in DIPs. -constexpr int kSmallAppIconSize = 20; - -} // namespace - namespace ash { -SmallAppIcon::SmallAppIcon(const gfx::Image& icon) { +AppIcon::AppIcon(const gfx::Image& icon, int size) { SetImage(gfx::ImageSkiaOperations::CreateResizedImage( icon.AsImageSkia(), skia::ImageOperations::RESIZE_BEST, - gfx::Size(kSmallAppIconSize, kSmallAppIconSize))); + gfx::Size(size, size))); } // views::View: -const char* SmallAppIcon::GetClassName() const { - return "SmallAppIcon"; +const char* AppIcon::GetClassName() const { + return "AppIcon"; } -} // namespace ash \ No newline at end of file +} // namespace ash diff --git a/ash/system/phonehub/phone_hub_app_icon.h b/ash/system/phonehub/phone_hub_app_icon.h new file mode 100644 index 0000000000000..c3568afadc89c --- /dev/null +++ b/ash/system/phonehub/phone_hub_app_icon.h @@ -0,0 +1,37 @@ +// Copyright 2022 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_ICON_H_ +#define ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_ICON_H_ +#include "ash/ash_export.h" +#include "ui/views/controls/image_view.h" + +namespace ash { + +class ASH_EXPORT AppIcon : public views::ImageView { + public: + // Measured in DIPs. + static constexpr int kSizeSmall = 20; + static constexpr int kSizeNormal = 32; + + static constexpr gfx::Size GetRecommendedImageSize(int icon_size) { + // Leave 1 DP of space around the image to avoid the appearance of clipping. + constexpr int kRecommendedImageInset = 1; + + int length_minus_insets = icon_size - 2 * kRecommendedImageInset; + return gfx::Size(length_minus_insets, length_minus_insets); + } + + AppIcon(const gfx::Image& icon, int size); + AppIcon(const AppIcon&) = delete; + AppIcon& operator=(const AppIcon&) = delete; + + ~AppIcon() override = default; + + // views::View: + const char* GetClassName() const override; +}; +} // namespace ash + +#endif // ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_ICON_H_ diff --git a/ash/system/phonehub/phone_hub_small_app_loading_icon.cc b/ash/system/phonehub/phone_hub_app_loading_icon.cc similarity index 73% rename from ash/system/phonehub/phone_hub_small_app_loading_icon.cc rename to ash/system/phonehub/phone_hub_app_loading_icon.cc index b3a031b883387..ded9626f1e9ec 100644 --- a/ash/system/phonehub/phone_hub_small_app_loading_icon.cc +++ b/ash/system/phonehub/phone_hub_app_loading_icon.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "phone_hub_small_app_loading_icon.h" +#include "phone_hub_app_loading_icon.h" #include "ash/style/ash_color_provider.h" #include "ui/compositor/layer.h" @@ -10,9 +10,13 @@ #include "ui/gfx/image/canvas_image_source.h" namespace ash { + +namespace { + class LoadingCircle : public gfx::CanvasImageSource { public: - explicit LoadingCircle() : CanvasImageSource(gfx::Size(18, 18)) {} + explicit LoadingCircle(int size) + : CanvasImageSource(AppIcon::GetRecommendedImageSize(size)) {} LoadingCircle(const LoadingCircle&) = delete; LoadingCircle& operator=(const LoadingCircle&) = delete; @@ -28,11 +32,15 @@ class LoadingCircle : public gfx::CanvasImageSource { } }; -SmallAppLoadingIcon::SmallAppLoadingIcon() - : SmallAppIcon( - gfx::Image(gfx::CanvasImageSource::MakeImageSkia())) { +} // namespace + +AppLoadingIcon::AppLoadingIcon(int size) + : AppIcon(gfx::Image( + gfx::CanvasImageSource::MakeImageSkia(size)), + size) { SetPaintToLayer(); layer()->SetFillsBoundsOpaquely(false); layer()->SetFillsBoundsCompletely(false); } -} // namespace ash \ No newline at end of file + +} // namespace ash diff --git a/ash/system/phonehub/phone_hub_app_loading_icon.h b/ash/system/phonehub/phone_hub_app_loading_icon.h new file mode 100644 index 0000000000000..3d85b692a057d --- /dev/null +++ b/ash/system/phonehub/phone_hub_app_loading_icon.h @@ -0,0 +1,20 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_LOADING_ICON_H_ +#define ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_LOADING_ICON_H_ + +#include "ash/ash_export.h" +#include "ash/system/phonehub/phone_hub_app_icon.h" + +namespace ash { + +class ASH_EXPORT AppLoadingIcon : public AppIcon { + public: + explicit AppLoadingIcon(int size); +}; + +} // namespace ash + +#endif // ASH_SYSTEM_PHONEHUB_PHONE_HUB_APP_LOADING_ICON_H_ diff --git a/ash/system/phonehub/phone_hub_more_apps_button.cc b/ash/system/phonehub/phone_hub_more_apps_button.cc index 8a52c0d36fef5..9810bbb691239 100644 --- a/ash/system/phonehub/phone_hub_more_apps_button.cc +++ b/ash/system/phonehub/phone_hub_more_apps_button.cc @@ -8,9 +8,9 @@ #include "ash/strings/grit/ash_strings.h" #include "ash/style/ash_color_provider.h" #include "ash/system/phonehub/phone_hub_app_count_icon.h" +#include "ash/system/phonehub/phone_hub_app_icon.h" +#include "ash/system/phonehub/phone_hub_app_loading_icon.h" #include "ash/system/phonehub/phone_hub_metrics.h" -#include "ash/system/phonehub/phone_hub_small_app_icon.h" -#include "ash/system/phonehub/phone_hub_small_app_loading_icon.h" #include "base/metrics/histogram_functions.h" #include "chromeos/ash/components/phonehub/app_stream_launcher_data_model.h" #include "ui/base/l10n/l10n_util.h" @@ -51,11 +51,16 @@ class MoreAppsButtonBackground : public views::Background { } }; +PhoneHubMoreAppsButton::PhoneHubMoreAppsButton() { + InitLayout(); +} + PhoneHubMoreAppsButton::PhoneHubMoreAppsButton( phonehub::AppStreamLauncherDataModel* app_stream_launcher_data_model, views::Button::PressedCallback callback) : views::Button(std::move(callback)), app_stream_launcher_data_model_(app_stream_launcher_data_model) { + CHECK(app_stream_launcher_data_model_); SetFocusBehavior(FocusBehavior::ALWAYS); SetAccessibleName( l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_FULL_APPS_LIST_BUTTON_TITLE)); @@ -64,7 +69,9 @@ PhoneHubMoreAppsButton::PhoneHubMoreAppsButton( } PhoneHubMoreAppsButton::~PhoneHubMoreAppsButton() { - app_stream_launcher_data_model_->RemoveObserver(this); + if (app_stream_launcher_data_model_) { + app_stream_launcher_data_model_->RemoveObserver(this); + } } void PhoneHubMoreAppsButton::InitLayout() { @@ -91,9 +98,15 @@ void PhoneHubMoreAppsButton::InitLayout() { kMoreAppsButtonRowPadding); SetEnabled(false); + SetBackground(std::make_unique()); + if (!app_stream_launcher_data_model_) { + AddLoadingAppIcons(/*animate=*/false); + return; + } + if (app_stream_launcher_data_model_->GetAppsListSortedByName()->empty()) { load_app_list_latency_ = base::TimeTicks::Now(); - InitGlimmer(); + AddLoadingAppIcons(/*animate=*/true); SetEnabled(false); phone_hub_metrics::LogMoreAppsButtonAnimationOnShow( phone_hub_metrics::MoreAppsButtonLoadingState::kAnimationShown); @@ -108,13 +121,16 @@ void PhoneHubMoreAppsButton::InitLayout() { phone_hub_metrics::LogMoreAppsButtonAnimationOnShow( phone_hub_metrics::MoreAppsButtonLoadingState::kMoreAppsButtonLoaded); } - - SetBackground(std::make_unique()); } -void PhoneHubMoreAppsButton::InitGlimmer() { +void PhoneHubMoreAppsButton::AddLoadingAppIcons(bool animate) { for (auto i = 0; i < 4; i++) { - auto* app_loading_icon = new SmallAppLoadingIcon(); + auto* app_loading_icon = + AddChildView(new AppLoadingIcon(AppIcon::kSizeSmall)); + if (!animate) { + continue; + } + views::AnimationBuilder animation_builder; animation_builder.Once().SetOpacity(app_loading_icon, kAnimationLoadingCardOpacity); @@ -131,7 +147,6 @@ void PhoneHubMoreAppsButton::InitGlimmer() { base::Milliseconds(kAnimationLoadingCardTransitDurationInMs)) .SetOpacity(app_loading_icon, kAnimationLoadingCardOpacity, gfx::Tween::LINEAR); - AddChildView(app_loading_icon); } } @@ -148,13 +163,15 @@ void PhoneHubMoreAppsButton::OnAppListChanged() { } void PhoneHubMoreAppsButton::LoadAppList() { + CHECK(app_stream_launcher_data_model_); RemoveAllChildViews(); const std::vector* app_list = app_stream_launcher_data_model_->GetAppsListSortedByName(); if (!app_list->empty()) { auto app_count = std::min(app_list->size(), size_t{3}); for (size_t i = 0; i < app_count; i++) { - AddChildView(std::make_unique(app_list->at(i).icon)); + AddChildView( + std::make_unique(app_list->at(i).icon, AppIcon::kSizeSmall)); } } diff --git a/ash/system/phonehub/phone_hub_more_apps_button.h b/ash/system/phonehub/phone_hub_more_apps_button.h index 6dfd804699222..f182556b9502a 100644 --- a/ash/system/phonehub/phone_hub_more_apps_button.h +++ b/ash/system/phonehub/phone_hub_more_apps_button.h @@ -20,9 +20,14 @@ class VIEWS_EXPORT PhoneHubMoreAppsButton public: METADATA_HEADER(PhoneHubMoreAppsButton); - explicit PhoneHubMoreAppsButton( + // Only use this constructor to create a skeleton view for the LoadingView. + // Does not process click events or load app icons. + PhoneHubMoreAppsButton(); + + PhoneHubMoreAppsButton( phonehub::AppStreamLauncherDataModel* app_stream_launcher_data_model, views::Button::PressedCallback callback); + PhoneHubMoreAppsButton(const PhoneHubMoreAppsButton&) = delete; PhoneHubMoreAppsButton& operator=(const PhoneHubMoreAppsButton&) = delete; ~PhoneHubMoreAppsButton() override; @@ -34,11 +39,12 @@ class VIEWS_EXPORT PhoneHubMoreAppsButton private: void InitLayout(); void LoadAppList(); - void InitGlimmer(); + void AddLoadingAppIcons(bool animate); base::TimeTicks load_app_list_latency_ = base::TimeTicks(); views::TableLayout* table_layout_ = nullptr; - phonehub::AppStreamLauncherDataModel* app_stream_launcher_data_model_; + phonehub::AppStreamLauncherDataModel* app_stream_launcher_data_model_ = + nullptr; }; } // namespace ash diff --git a/ash/system/phonehub/phone_hub_recent_apps_view.cc b/ash/system/phonehub/phone_hub_recent_apps_view.cc index 4871cbf533431..686a126069b6b 100644 --- a/ash/system/phonehub/phone_hub_recent_apps_view.cc +++ b/ash/system/phonehub/phone_hub_recent_apps_view.cc @@ -14,6 +14,7 @@ #include "ash/style/ash_color_id.h" #include "ash/style/ash_color_provider.h" #include "ash/system/phonehub/phone_connected_view.h" +#include "ash/system/phonehub/phone_hub_app_loading_icon.h" #include "ash/system/phonehub/phone_hub_more_apps_button.h" #include "ash/system/phonehub/phone_hub_recent_app_button.h" #include "ash/system/phonehub/phone_hub_view_ids.h" @@ -28,6 +29,7 @@ #include "ui/gfx/geometry/insets.h" #include "ui/gfx/image/image_skia_operations.h" #include "ui/gfx/paint_vector_icon.h" +#include "ui/views/animation/animation_builder.h" #include "ui/views/background.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/image_button.h" @@ -65,8 +67,65 @@ constexpr int kMaxAppsWithMoreAppsButton = 5; constexpr gfx::Rect kMoreAppsButtonArea = gfx::Rect(57, 32); constexpr int kMoreAppsButtonRadius = 16; +// Animation constants for loading view +constexpr float kAnimationLoadingIconOpacityHigh = 1.0f; +constexpr float kAnimationLoadingIconOpacityLow = 0.5f; +constexpr int kAnimationLoadingIconStaggerDelayInMs = 100; +constexpr int kAnimationLoadingIconFadeDownDurationInMs = 500; +constexpr int kAnimationLoadingIconFadeUpDurationInMs = 500; + constexpr int kRecentAppsHeaderSpacing = 220; +void LayoutAppButtonsView(views::View* buttons_view) { + const gfx::Rect child_area = buttons_view->GetContentsBounds(); + views::View::Views visible_children; + base::ranges::copy_if( + buttons_view->children(), std::back_inserter(visible_children), + [](const auto* v) { + return v->GetVisible() && (v->GetPreferredSize().width() > 0); + }); + if (visible_children.empty()) { + return; + } + const int visible_child_width = + std::accumulate(visible_children.cbegin(), visible_children.cend(), 0, + [](int width, const auto* v) { + return width + v->GetPreferredSize().width(); + }); + + int spacing = 0; + if (visible_children.size() > 1) { + spacing = (child_area.width() - visible_child_width - + kRecentAppButtonsViewHorizontalPadding * 2) / + (static_cast(visible_children.size()) - 1); + spacing = base::clamp(spacing, kRecentAppButtonMinSpacing, + kRecentAppButtonDefaultSpacing); + } + + int child_x = child_area.x() + kRecentAppButtonsViewHorizontalPadding; + int child_y = child_area.y() + kRecentAppButtonsViewTopPadding + + kRecentAppButtonFocusPadding.bottom(); + for (auto* child : visible_children) { + // Most recent apps be added to the left and shift right as the other apps + // are streamed. + int width = child->GetPreferredSize().width(); + child->SetBounds(child_x, child_y, width, child->GetHeightForWidth(width)); + child_x += width + spacing; + } +} + +void ApplyLoadingAnimation(views::View* view) { + views::AnimationBuilder() + .Repeatedly() + .SetDuration( + base::Milliseconds(kAnimationLoadingIconFadeDownDurationInMs)) + .SetOpacity(view, kAnimationLoadingIconOpacityLow, + gfx::Tween::ACCEL_30_DECEL_20_85) + .Then() + .SetDuration(base::Milliseconds(kAnimationLoadingIconFadeUpDurationInMs)) + .SetOpacity(view, kAnimationLoadingIconOpacityHigh, gfx::Tween::LINEAR); +} + class HeaderView : public views::View { public: explicit HeaderView(views::ImageButton::PressedCallback callback) { @@ -158,6 +217,10 @@ PhoneHubRecentAppsView::PhoneHubRecentAppsView( AddChildView(std::make_unique()); placeholder_view_ = AddChildView(std::make_unique()); + if (features::IsEcheNetworkConnectionStateEnabled()) { + loading_view_ = AddChildView(std::make_unique()); + } + Update(); recent_apps_interaction_handler_->AddObserver(this); } @@ -214,41 +277,7 @@ void PhoneHubRecentAppsView::RecentAppButtonsView::Layout() { views::View::Layout(); return; } - - const gfx::Rect child_area = GetContentsBounds(); - views::View::Views visible_children; - base::ranges::copy_if( - children(), std::back_inserter(visible_children), [](const auto* v) { - return v->GetVisible() && (v->GetPreferredSize().width() > 0); - }); - if (visible_children.empty()) { - return; - } - const int visible_child_width = - std::accumulate(visible_children.cbegin(), visible_children.cend(), 0, - [](int width, const auto* v) { - return width + v->GetPreferredSize().width(); - }); - - int spacing = 0; - if (visible_children.size() > 1) { - spacing = (child_area.width() - visible_child_width - - kRecentAppButtonsViewHorizontalPadding * 2) / - (static_cast(visible_children.size()) - 1); - spacing = base::clamp(spacing, kRecentAppButtonMinSpacing, - kRecentAppButtonDefaultSpacing); - } - - int child_x = child_area.x() + kRecentAppButtonsViewHorizontalPadding; - int child_y = child_area.y() + kRecentAppButtonsViewTopPadding + - kRecentAppButtonFocusPadding.bottom(); - for (auto* child : visible_children) { - // Most recent apps be added to the left and shift right as the other apps - // are streamed. - int width = child->GetPreferredSize().width(); - child->SetBounds(child_x, child_y, width, child->GetHeightForWidth(width)); - child_x += width + spacing; - } + LayoutAppButtonsView(this); } const char* PhoneHubRecentAppsView::RecentAppButtonsView::GetClassName() const { @@ -259,6 +288,59 @@ void PhoneHubRecentAppsView::RecentAppButtonsView::Reset() { RemoveAllChildViews(); } +PhoneHubRecentAppsView::LoadingView::LoadingView() { + SetOrientation(views::BoxLayout::Orientation::kHorizontal); + SetDefaultFlex(1); + SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kCenter); + SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter); + + PopulateLoadingView(); +} + +PhoneHubRecentAppsView::LoadingView::~LoadingView() = default; + +gfx::Size PhoneHubRecentAppsView::LoadingView::CalculatePreferredSize() const { + int width = kTrayMenuWidth - kBubbleHorizontalSidePaddingDip * 2; + int height = kMoreAppsButtonSize + kRecentAppButtonFocusPadding.height() + + kRecentAppButtonsViewTopPadding; + + return gfx::Size(width, height); +} + +void PhoneHubRecentAppsView::LoadingView::Layout() { + if (features::IsEcheLauncherIconsInMoreAppsButtonEnabled()) { + views::View::Layout(); + return; + } + LayoutAppButtonsView(this); +} + +const char* PhoneHubRecentAppsView::LoadingView::GetClassName() const { + return "RecentAppLoadingView"; +} + +void PhoneHubRecentAppsView::LoadingView::PopulateLoadingView() { + for (size_t i = 0; i < 6; i++) { + views::View* empty_app_loading_icon = nullptr; + if (i == 5) { + empty_app_loading_icon = AddChildView(new PhoneHubMoreAppsButton()); + + // TODO(b/271478560): Animation fails for more apps button. + continue; + } else { + empty_app_loading_icon = + AddChildView(new AppLoadingIcon(AppIcon::kSizeNormal)); + } + + auto timer = std::make_unique(); + timer->Start( + FROM_HERE, + /*delay=*/base::Milliseconds(i * kAnimationLoadingIconStaggerDelayInMs), + base::BindOnce(&ApplyLoadingAnimation, empty_app_loading_icon)); + timers_.emplace_back(std::move(timer)); + } +} + void PhoneHubRecentAppsView::Update() { recent_app_buttons_view_->Reset(); recent_app_button_list_.clear(); @@ -269,6 +351,9 @@ void PhoneHubRecentAppsView::Update() { switch (current_ui_state) { case RecentAppsUiState::HIDDEN: placeholder_view_->SetVisible(false); + if (loading_view_) { + loading_view_->SetVisible(false); + } SetVisible(false); break; case RecentAppsUiState::LOADING: @@ -281,6 +366,9 @@ void PhoneHubRecentAppsView::Update() { case RecentAppsUiState::PLACEHOLDER_VIEW: recent_app_buttons_view_->SetVisible(false); placeholder_view_->SetVisible(true); + if (loading_view_) { + loading_view_->SetVisible(false); + } SetVisible(true); break; case RecentAppsUiState::ITEMS_VISIBLE: @@ -308,6 +396,9 @@ void PhoneHubRecentAppsView::Update() { recent_app_buttons_view_->SetVisible(true); placeholder_view_->SetVisible(false); + if (loading_view_) { + loading_view_->SetVisible(false); + } SetVisible(true); break; } diff --git a/ash/system/phonehub/phone_hub_recent_apps_view.h b/ash/system/phonehub/phone_hub_recent_apps_view.h index e3e1aa1b926d7..ca22b58b71b8e 100644 --- a/ash/system/phonehub/phone_hub_recent_apps_view.h +++ b/ash/system/phonehub/phone_hub_recent_apps_view.h @@ -10,8 +10,10 @@ #include "ash/ash_export.h" #include "ash/system/phonehub/phone_connected_view.h" #include "base/gtest_prod_util.h" +#include "base/timer/timer.h" #include "chromeos/ash/components/phonehub/recent_apps_interaction_handler.h" #include "ui/views/controls/button/image_button.h" +#include "ui/views/layout/box_layout_view.h" #include "ui/views/view.h" #include "ui/views/view_model.h" @@ -69,6 +71,24 @@ class ASH_EXPORT PhoneHubRecentAppsView void Reset(); }; + class LoadingView : public views::BoxLayoutView { + public: + LoadingView(); + ~LoadingView() override; + LoadingView(LoadingView&) = delete; + LoadingView operator=(LoadingView&) = delete; + + // views::View: + gfx::Size CalculatePreferredSize() const override; + void Layout() override; + const char* GetClassName() const override; + + void PopulateLoadingView(); + + private: + std::vector> timers_; + }; + // Update the view to reflect the most recently opened apps. void Update(); @@ -86,6 +106,7 @@ class ASH_EXPORT PhoneHubRecentAppsView nullptr; phonehub::PhoneHubManager* phone_hub_manager_ = nullptr; PlaceholderView* placeholder_view_ = nullptr; + LoadingView* loading_view_ = nullptr; PhoneConnectedView* connected_view_ = nullptr; }; diff --git a/ash/system/phonehub/phone_hub_small_app_icon.h b/ash/system/phonehub/phone_hub_small_app_icon.h deleted file mode 100644 index 9c59fb637a53e..0000000000000 --- a/ash/system/phonehub/phone_hub_small_app_icon.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2022 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ASH_SYSTEM_PHONEHUB_PHONE_HUB_SMALL_APP_ICON_H_ -#define ASH_SYSTEM_PHONEHUB_PHONE_HUB_SMALL_APP_ICON_H_ -#include "ash/ash_export.h" -#include "ui/views/controls/image_view.h" - -namespace ash { - -class ASH_EXPORT SmallAppIcon : public views::ImageView { - public: - SmallAppIcon(const gfx::Image& icon); - SmallAppIcon(const SmallAppIcon&) = delete; - SmallAppIcon& operator=(const SmallAppIcon&) = delete; - - ~SmallAppIcon() override = default; - - // views::View: - const char* GetClassName() const override; -}; -} // namespace ash - -#endif // ASH_SYSTEM_PHONEHUB_PHONE_HUB_SMALL_APP_ICON_H_ \ No newline at end of file diff --git a/ash/system/phonehub/phone_hub_small_app_loading_icon.h b/ash/system/phonehub/phone_hub_small_app_loading_icon.h deleted file mode 100644 index 5d39a3c173401..0000000000000 --- a/ash/system/phonehub/phone_hub_small_app_loading_icon.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2023 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ASH_SYSTEM_PHONEHUB_PHONE_HUB_SMALL_APP_LOADING_ICON_H_ -#define ASH_SYSTEM_PHONEHUB_PHONE_HUB_SMALL_APP_LOADING_ICON_H_ -#include "ash/ash_export.h" -#include "ash/system/phonehub/phone_hub_small_app_icon.h" - -namespace ash { - -class ASH_EXPORT SmallAppLoadingIcon : public SmallAppIcon { - public: - explicit SmallAppLoadingIcon(); -}; -} // namespace ash - -#endif // ASH_SYSTEM_PHONEHUB_PHONE_HUB_SMALL_APP_LOADING_ICON_H_ \ No newline at end of file