Commit 7d884691 authored by Matthew Mourgos's avatar Matthew Mourgos Committed by Commit Bot

CrOS AppList: Add app badging to apps in the launcher

Bug: 1122723
Change-Id: I861fd0f8fd6ae76863fd1828829d62549ca53eaa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2368423
Commit-Queue: Matthew Mourgos <mmourgos@chromium.org>
Reviewed-by: default avatarToni Baržić <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#802749}
parent edcd9248
...@@ -219,6 +219,7 @@ test("app_list_unittests") { ...@@ -219,6 +219,7 @@ test("app_list_unittests") {
sources = [ sources = [
"folder_image_unittest.cc", "folder_image_unittest.cc",
"test/run_all_unittests.cc", "test/run_all_unittests.cc",
"views/app_list_item_view_unittest.cc",
"views/app_list_main_view_unittest.cc", "views/app_list_main_view_unittest.cc",
"views/app_list_menu_model_adapter_unittest.cc", "views/app_list_menu_model_adapter_unittest.cc",
"views/app_list_view_unittest.cc", "views/app_list_view_unittest.cc",
......
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
#include "components/pref_registry/pref_registry_syncable.h" #include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
#include "extensions/common/constants.h" #include "extensions/common/constants.h"
#include "ui/base/ui_base_features.h" #include "ui/base/ui_base_features.h"
#include "ui/display/manager/display_manager.h" #include "ui/display/manager/display_manager.h"
...@@ -188,7 +189,9 @@ GetTransitionFromMetricsAnimationInfo( ...@@ -188,7 +189,9 @@ GetTransitionFromMetricsAnimationInfo(
AppListControllerImpl::AppListControllerImpl() AppListControllerImpl::AppListControllerImpl()
: model_(std::make_unique<AppListModel>()), : model_(std::make_unique<AppListModel>()),
presenter_(std::make_unique<AppListPresenterDelegateImpl>(this)) { presenter_(std::make_unique<AppListPresenterDelegateImpl>(this)),
is_notification_indicator_enabled_(
::features::IsNotificationIndicatorEnabled()) {
model_->AddObserver(this); model_->AddObserver(this);
SessionControllerImpl* session_controller = SessionControllerImpl* session_controller =
...@@ -499,10 +502,35 @@ bool AppListControllerImpl::IsVisible( ...@@ -499,10 +502,35 @@ bool AppListControllerImpl::IsVisible(
void AppListControllerImpl::OnAppListItemAdded(AppListItem* item) { void AppListControllerImpl::OnAppListItemAdded(AppListItem* item) {
client_->OnItemAdded(profile_id_, item->CloneMetadata()); client_->OnItemAdded(profile_id_, item->CloneMetadata());
if (is_notification_indicator_enabled_ && cache_) {
// Update the notification badge indicator for the newly added app list
// item.
cache_->ForOneApp(item->id(), [item](const apps::AppUpdate& update) {
item->UpdateBadge(update.HasBadge() == apps::mojom::OptionalBool::kTrue);
});
}
} }
void AppListControllerImpl::OnActiveUserPrefServiceChanged( void AppListControllerImpl::OnActiveUserPrefServiceChanged(
PrefService* /* pref_service */) { PrefService* /* pref_service */) {
if (is_notification_indicator_enabled_) {
// Observe AppRegistryCache for the current active account to get
// notification updates.
AccountId account_id =
Shell::Get()->session_controller()->GetActiveAccountId();
cache_ =
apps::AppRegistryCacheWrapper::Get().GetAppRegistryCache(account_id);
Observe(cache_);
if (cache_) {
// Update the notification badge indicator for all apps in the app list.
cache_->ForEachApp([this](const apps::AppUpdate& update) {
UpdateItemNotificationBadge(update.AppId(), update.HasBadge());
});
}
}
if (!IsTabletMode()) { if (!IsTabletMode()) {
DismissAppList(); DismissAppList();
return; return;
...@@ -1810,6 +1838,17 @@ gfx::Rect AppListControllerImpl::GetInitialAppListItemScreenBoundsForWindow( ...@@ -1810,6 +1838,17 @@ gfx::Rect AppListControllerImpl::GetInitialAppListItemScreenBoundsForWindow(
app_id ? *app_id : std::string()); app_id ? *app_id : std::string());
} }
void AppListControllerImpl::OnAppUpdate(const apps::AppUpdate& update) {
if (update.HasBadgeChanged()) {
UpdateItemNotificationBadge(update.AppId(), update.HasBadge());
}
}
void AppListControllerImpl::OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) {
Observe(nullptr);
}
void AppListControllerImpl::UpdateTrackedAppWindow() { void AppListControllerImpl::UpdateTrackedAppWindow() {
aura::Window* top_window = GetTopVisibleWindow(); aura::Window* top_window = GetTopVisibleWindow();
if (tracked_app_window_ == top_window) if (tracked_app_window_ == top_window)
...@@ -1827,4 +1866,12 @@ void AppListControllerImpl::RecordAppListState() { ...@@ -1827,4 +1866,12 @@ void AppListControllerImpl::RecordAppListState() {
recorded_app_list_visibility_ = last_visible_; recorded_app_list_visibility_ = last_visible_;
} }
void AppListControllerImpl::UpdateItemNotificationBadge(
const std::string& app_id,
apps::mojom::OptionalBool has_badge) {
AppListItem* item = model_->FindItem(app_id);
if (item)
item->UpdateBadge(has_badge == apps::mojom::OptionalBool::kTrue);
}
} // namespace ash } // namespace ash
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "ash/wm/mru_window_tracker.h" #include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_observer.h" #include "ash/wm/overview/overview_observer.h"
#include "base/observer_list.h" #include "base/observer_list.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/sync/model/string_ordinal.h" #include "components/sync/model/string_ordinal.h"
#include "ui/aura/window_observer.h" #include "ui/aura/window_observer.h"
#include "ui/display/types/display_constants.h" #include "ui/display/types/display_constants.h"
...@@ -49,22 +50,24 @@ class AppListControllerObserver; ...@@ -49,22 +50,24 @@ class AppListControllerObserver;
// Ash's AppListController owns the AppListModel and implements interface // Ash's AppListController owns the AppListModel and implements interface
// functions that allow Chrome to modify and observe the Shelf and AppListModel // functions that allow Chrome to modify and observe the Shelf and AppListModel
// state. // state.
class ASH_EXPORT AppListControllerImpl : public AppListController, class ASH_EXPORT AppListControllerImpl
public SessionObserver, : public AppListController,
public AppListModelObserver, public SessionObserver,
public AppListViewDelegate, public AppListModelObserver,
public ShellObserver, public AppListViewDelegate,
public OverviewObserver, public ShellObserver,
public TabletModeObserver, public OverviewObserver,
public KeyboardControllerObserver, public TabletModeObserver,
public WallpaperControllerObserver, public KeyboardControllerObserver,
public AssistantStateObserver, public WallpaperControllerObserver,
public WindowTreeHostManager::Observer, public AssistantStateObserver,
public aura::WindowObserver, public WindowTreeHostManager::Observer,
public MruWindowTracker::Observer, public aura::WindowObserver,
public AssistantControllerObserver, public MruWindowTracker::Observer,
public AssistantUiModelObserver, public AssistantControllerObserver,
public HomeScreenDelegate { public AssistantUiModelObserver,
public HomeScreenDelegate,
public apps::AppRegistryCache::Observer {
public: public:
AppListControllerImpl(); AppListControllerImpl();
~AppListControllerImpl() override; ~AppListControllerImpl() override;
...@@ -295,6 +298,11 @@ class ASH_EXPORT AppListControllerImpl : public AppListController, ...@@ -295,6 +298,11 @@ class ASH_EXPORT AppListControllerImpl : public AppListController,
gfx::Rect GetInitialAppListItemScreenBoundsForWindow( gfx::Rect GetInitialAppListItemScreenBoundsForWindow(
aura::Window* window) override; aura::Window* window) override;
// apps::AppRegistryCache::Observer:
void OnAppUpdate(const apps::AppUpdate& update) override;
void OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) override;
bool onscreen_keyboard_shown() const { return onscreen_keyboard_shown_; } bool onscreen_keyboard_shown() const { return onscreen_keyboard_shown_; }
HomeLauncherTransitionState home_launcher_transition_state() const { HomeLauncherTransitionState home_launcher_transition_state() const {
...@@ -380,6 +388,11 @@ class ASH_EXPORT AppListControllerImpl : public AppListController, ...@@ -380,6 +388,11 @@ class ASH_EXPORT AppListControllerImpl : public AppListController,
// Updates the window that is tracked as |tracked_app_window_|. // Updates the window that is tracked as |tracked_app_window_|.
void UpdateTrackedAppWindow(); void UpdateTrackedAppWindow();
// Updates whether a notification badge is shown for the AppListItemView
// corresponding with the |app_id|.
void UpdateItemNotificationBadge(const std::string& app_id,
apps::mojom::OptionalBool has_badge);
// Whether the home launcher is // Whether the home launcher is
// * being shown (either through an animation or a drag) // * being shown (either through an animation or a drag)
// * being hidden (either through an animation or a drag) // * being hidden (either through an animation or a drag)
...@@ -457,6 +470,13 @@ class ASH_EXPORT AppListControllerImpl : public AppListController, ...@@ -457,6 +470,13 @@ class ASH_EXPORT AppListControllerImpl : public AppListController,
base::ObserverList<AppListControllerObserver> observers_; base::ObserverList<AppListControllerObserver> observers_;
// Observed to update notification badging on app list items. Also used to get
// initial notification badge information when app list items are added.
apps::AppRegistryCache* cache_ = nullptr;
// Whether the notification indicator flag is enabled.
const bool is_notification_indicator_enabled_;
DISALLOW_COPY_AND_ASSIGN(AppListControllerImpl); DISALLOW_COPY_AND_ASSIGN(AppListControllerImpl);
}; };
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
#include "base/test/with_feature_override.h" #include "base/test/with_feature_override.h"
#include "chromeos/constants/chromeos_features.h" #include "chromeos/constants/chromeos_features.h"
#include "chromeos/constants/chromeos_switches.h" #include "chromeos/constants/chromeos_switches.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h" #include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/test/event_generator.h" #include "ui/events/test/event_generator.h"
#include "ui/message_center/message_center.h" #include "ui/message_center/message_center.h"
...@@ -641,6 +642,61 @@ TEST_F(AppListControllerImplTest, ...@@ -641,6 +642,61 @@ TEST_F(AppListControllerImplTest,
EXPECT_FALSE(GetAppListView()->GetWidget()->GetNativeWindow()->IsVisible()); EXPECT_FALSE(GetAppListView()->GetWidget()->GetNativeWindow()->IsVisible());
} }
class AppListControllerImplTestWithNotificationBadging
: public AppListControllerImplTest {
public:
AppListControllerImplTestWithNotificationBadging() {
scoped_features_.InitWithFeatures({::features::kNotificationIndicator}, {});
}
AppListControllerImplTestWithNotificationBadging(
const AppListControllerImplTestWithNotificationBadging& other) = delete;
AppListControllerImplTestWithNotificationBadging& operator=(
const AppListControllerImplTestWithNotificationBadging& other) = delete;
~AppListControllerImplTestWithNotificationBadging() override = default;
void UpdateAppHasBadge(const std::string& app_id, bool app_has_badge) {
AppListControllerImpl* controller = Shell::Get()->app_list_controller();
AccountId account_id = AccountId::FromUserEmail("test@gmail.com");
apps::mojom::App test_app;
test_app.app_id = app_id;
if (app_has_badge)
test_app.has_badge = apps::mojom::OptionalBool::kTrue;
else
test_app.has_badge = apps::mojom::OptionalBool::kFalse;
apps::AppUpdate test_update(nullptr, &test_app /* delta */, account_id);
static_cast<apps::AppRegistryCache::Observer*>(controller)
->OnAppUpdate(test_update);
}
private:
base::test::ScopedFeatureList scoped_features_;
};
// Tests that when an app has an update to its notification badge, the change
// gets propagated to the corresponding AppListItemView.
TEST_F(AppListControllerImplTestWithNotificationBadging,
NotificationBadgeUpdateTest) {
PopulateItem(1);
ShowAppListNow();
test::AppsGridViewTestApi apps_grid_view_test_api(GetAppsGridView());
const AppListItemView* item_view =
apps_grid_view_test_api.GetViewAtIndex(GridIndex(0, 0));
ASSERT_TRUE(item_view);
const std::string app_id = item_view->item()->id();
EXPECT_FALSE(item_view->IsNotificationIndicatorShownForTest());
UpdateAppHasBadge(app_id, /*has_badge=*/true);
EXPECT_TRUE(item_view->IsNotificationIndicatorShownForTest());
UpdateAppHasBadge(app_id, /*has_badge=*/false);
EXPECT_FALSE(item_view->IsNotificationIndicatorShownForTest());
}
class AppListControllerImplTestWithoutHotseat class AppListControllerImplTestWithoutHotseat
: public AppListControllerImplTest { : public AppListControllerImplTest {
public: public:
......
...@@ -90,4 +90,10 @@ void AppListItem::SetNameAndShortName(const std::string& name, ...@@ -90,4 +90,10 @@ void AppListItem::SetNameAndShortName(const std::string& name,
observer.ItemNameChanged(); observer.ItemNameChanged();
} }
void AppListItem::UpdateBadge(bool has_badge) {
for (auto& observer : observers_) {
observer.ItemBadgeVisibilityChanged(has_badge);
}
}
} // namespace ash } // namespace ash
...@@ -107,6 +107,9 @@ class APP_LIST_MODEL_EXPORT AppListItem { ...@@ -107,6 +107,9 @@ class APP_LIST_MODEL_EXPORT AppListItem {
void SetNameAndShortName(const std::string& name, void SetNameAndShortName(const std::string& name,
const std::string& short_name); const std::string& short_name);
// Updates whether the notification badge is shown on the view.
void UpdateBadge(bool has_badge);
void set_position(const syncer::StringOrdinal& new_position) { void set_position(const syncer::StringOrdinal& new_position) {
DCHECK(new_position.IsValid()); DCHECK(new_position.IsValid());
metadata_->position = new_position; metadata_->position = new_position;
......
...@@ -20,6 +20,9 @@ class APP_LIST_MODEL_EXPORT AppListItemObserver { ...@@ -20,6 +20,9 @@ class APP_LIST_MODEL_EXPORT AppListItemObserver {
// Invoked after item's name is changed. // Invoked after item's name is changed.
virtual void ItemNameChanged() {} virtual void ItemNameChanged() {}
// Invoked when the item's notification badge visibility is changed.
virtual void ItemBadgeVisibilityChanged(bool is_badge_visible) {}
// Invoked when the item is about to be destroyed. // Invoked when the item is about to be destroyed.
virtual void ItemBeingDestroyed() {} virtual void ItemBeingDestroyed() {}
......
...@@ -24,10 +24,12 @@ ...@@ -24,10 +24,12 @@
#include "cc/paint/paint_flags.h" #include "cc/paint/paint_flags.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h" #include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h" #include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/animation/throb_animation.h" #include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/color_palette.h" #include "ui/gfx/color_palette.h"
#include "ui/gfx/font_list.h" #include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point.h"
...@@ -35,6 +37,7 @@ ...@@ -35,6 +37,7 @@
#include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/image/canvas_image_source.h" #include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia_operations.h" #include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/shadow_value.h" #include "ui/gfx/shadow_value.h"
#include "ui/gfx/transform_util.h" #include "ui/gfx/transform_util.h"
#include "ui/strings/grit/ui_strings.h" #include "ui/strings/grit/ui_strings.h"
...@@ -95,6 +98,34 @@ constexpr int kIconShadowBlur = 10; ...@@ -95,6 +98,34 @@ constexpr int kIconShadowBlur = 10;
// The shadow color of icon. // The shadow color of icon.
constexpr SkColor kIconShadowColor = SkColorSetA(SK_ColorBLACK, 31); constexpr SkColor kIconShadowColor = SkColorSetA(SK_ColorBLACK, 31);
constexpr int kNotificationIndicatorRadiusDip = 7;
constexpr SkColor kDefaultIndicatorColor = SK_ColorWHITE;
// Uses the icon image to calculate the light vibrant color to be used for
// the notification indicator.
base::Optional<SkColor> CalculateNotificationColor(gfx::ImageSkia image) {
const SkBitmap* source = image.bitmap();
if (!source || source->empty() || source->isNull())
return base::nullopt;
std::vector<color_utils::ColorProfile> color_profiles;
color_profiles.push_back(color_utils::ColorProfile(
color_utils::LumaRange::LIGHT, color_utils::SaturationRange::VIBRANT));
std::vector<color_utils::Swatch> best_swatches =
color_utils::CalculateProminentColorsOfBitmap(
*source, color_profiles, nullptr /* bitmap region */,
color_utils::ColorSwatchFilter());
// If the best swatch color is transparent, then
// CalculateProminentColorsOfBitmap() failed to find a suitable color.
if (best_swatches.empty() || best_swatches[0].color == SK_ColorTRANSPARENT)
return base::nullopt;
return best_swatches[0].color;
}
// The class clips the provided folder icon image. // The class clips the provided folder icon image.
class ClippedFolderIconImageSource : public gfx::CanvasImageSource { class ClippedFolderIconImageSource : public gfx::CanvasImageSource {
public: public:
...@@ -126,6 +157,56 @@ class ClippedFolderIconImageSource : public gfx::CanvasImageSource { ...@@ -126,6 +157,56 @@ class ClippedFolderIconImageSource : public gfx::CanvasImageSource {
} // namespace } // namespace
// The badge which is activated when the app corresponding with this
// AppListItemView receives a notification.
class AppListItemView::AppNotificationIndicatorView : public views::View {
public:
explicit AppNotificationIndicatorView(SkColor indicator_color)
: indicator_color_(indicator_color) {}
AppNotificationIndicatorView(const AppNotificationIndicatorView& other) =
delete;
AppNotificationIndicatorView& operator=(
const AppNotificationIndicatorView& other) = delete;
~AppNotificationIndicatorView() override = default;
void OnPaint(gfx::Canvas* canvas) override {
gfx::ScopedCanvas scoped(canvas);
canvas->SaveLayerAlpha(SK_AlphaOPAQUE);
DCHECK_EQ(width(), height());
DCHECK_EQ(kNotificationIndicatorRadiusDip, width() / 2);
const float dsf = canvas->UndoDeviceScaleFactor();
const int kStrokeWidthPx = 1;
gfx::PointF center = gfx::RectF(GetLocalBounds()).CenterPoint();
center.Scale(dsf);
// Fill the center.
cc::PaintFlags flags;
flags.setColor(indicator_color_);
flags.setAntiAlias(true);
canvas->DrawCircle(
center, dsf * kNotificationIndicatorRadiusDip - kStrokeWidthPx, flags);
// Stroke the border.
flags.setColor(SkColorSetA(SK_ColorBLACK, 0x4D));
flags.setStyle(cc::PaintFlags::kStroke_Style);
canvas->DrawCircle(
center, dsf * kNotificationIndicatorRadiusDip - kStrokeWidthPx / 2.0f,
flags);
}
void SetColor(SkColor new_color) {
indicator_color_ = new_color;
SchedulePaint();
}
SkColor GetColorForTest() { return indicator_color_; }
private:
SkColor indicator_color_;
};
// ImageView for the item icon. // ImageView for the item icon.
class AppListItemView::IconImageView : public views::ImageView { class AppListItemView::IconImageView : public views::ImageView {
public: public:
...@@ -235,7 +316,9 @@ AppListItemView::AppListItemView(AppsGridView* apps_grid_view, ...@@ -235,7 +316,9 @@ AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
is_folder_(item->GetItemType() == AppListFolderItem::kItemType), is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
item_weak_(item), item_weak_(item),
delegate_(delegate), delegate_(delegate),
apps_grid_view_(apps_grid_view) { apps_grid_view_(apps_grid_view),
is_notification_indicator_enabled_(
features::IsNotificationIndicatorEnabled()) {
SetFocusBehavior(FocusBehavior::ALWAYS); SetFocusBehavior(FocusBehavior::ALWAYS);
if (!is_in_folder && !is_folder_) { if (!is_in_folder && !is_folder_) {
...@@ -275,6 +358,14 @@ AppListItemView::AppListItemView(AppsGridView* apps_grid_view, ...@@ -275,6 +358,14 @@ AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
false /*animate*/); false /*animate*/);
} }
if (is_notification_indicator_enabled_ && !is_folder_) {
notification_indicator_ = AddChildView(
std::make_unique<AppNotificationIndicatorView>(kDefaultIndicatorColor));
notification_indicator_->SetPaintToLayer();
notification_indicator_->layer()->SetFillsBoundsOpaquely(false);
notification_indicator_->SetVisible(false);
}
title_ = AddChildView(std::move(title)); title_ = AddChildView(std::move(title));
SetIcon(item->GetIcon(GetAppListConfig().type())); SetIcon(item->GetIcon(GetAppListConfig().type()));
...@@ -326,6 +417,13 @@ void AppListItemView::SetIcon(const gfx::ImageSkia& icon) { ...@@ -326,6 +417,13 @@ void AppListItemView::SetIcon(const gfx::ImageSkia& icon) {
icon_shadow_->SetImage(shadowed); icon_shadow_->SetImage(shadowed);
} }
if (is_notification_indicator_enabled_ && notification_indicator_) {
base::Optional<SkColor> notification_color =
CalculateNotificationColor(icon_image_);
notification_indicator_->SetColor(
notification_color.value_or(kDefaultIndicatorColor));
}
Layout(); Layout();
} }
...@@ -683,6 +781,13 @@ void AppListItemView::Layout() { ...@@ -683,6 +781,13 @@ void AppListItemView::Layout() {
if (!apps_grid_view_->is_in_folder()) if (!apps_grid_view_->is_in_folder())
title_bounds.Inset(title_shadow_margins_); title_bounds.Inset(title_shadow_margins_);
title_->SetBoundsRect(title_bounds); title_->SetBoundsRect(title_bounds);
if (is_notification_indicator_enabled_ && notification_indicator_) {
notification_indicator_->SetBoundsRect(
gfx::Rect(icon_bounds.right() - 2 * kNotificationIndicatorRadiusDip - 1,
icon_bounds.y() + 1, kNotificationIndicatorRadiusDip * 2,
kNotificationIndicatorRadiusDip * 2));
}
} }
gfx::Size AppListItemView::CalculatePreferredSize() const { gfx::Size AppListItemView::CalculatePreferredSize() const {
...@@ -882,6 +987,14 @@ bool AppListItemView::FireTouchDragTimerForTest() { ...@@ -882,6 +987,14 @@ bool AppListItemView::FireTouchDragTimerForTest() {
return true; return true;
} }
bool AppListItemView::IsNotificationIndicatorShownForTest() const {
return notification_indicator_ && notification_indicator_->GetVisible();
}
SkColor AppListItemView::GetNotificationIndicatorColorForTest() const {
return notification_indicator_->GetColorForTest();
}
void AppListItemView::AnimationProgressed(const gfx::Animation* animation) { void AppListItemView::AnimationProgressed(const gfx::Animation* animation) {
DCHECK(!is_folder_); DCHECK(!is_folder_);
...@@ -1004,6 +1117,11 @@ void AppListItemView::ItemNameChanged() { ...@@ -1004,6 +1117,11 @@ void AppListItemView::ItemNameChanged() {
base::UTF8ToUTF16(item_weak_->name())); base::UTF8ToUTF16(item_weak_->name()));
} }
void AppListItemView::ItemBadgeVisibilityChanged(bool is_badge_visible) {
if (is_notification_indicator_enabled_ && notification_indicator_ && icon_)
notification_indicator_->SetVisible(is_badge_visible);
}
void AppListItemView::ItemBeingDestroyed() { void AppListItemView::ItemBeingDestroyed() {
DCHECK(item_weak_); DCHECK(item_weak_);
item_weak_->RemoveObserver(this); item_weak_->RemoveObserver(this);
......
...@@ -147,8 +147,13 @@ class APP_LIST_EXPORT AppListItemView : public views::Button, ...@@ -147,8 +147,13 @@ class APP_LIST_EXPORT AppListItemView : public views::Button,
bool is_folder() const { return is_folder_; } bool is_folder() const { return is_folder_; }
bool IsNotificationIndicatorShownForTest() const;
SkColor GetNotificationIndicatorColorForTest() const;
private: private:
class IconImageView; class IconImageView;
class AppNotificationIndicatorView;
enum UIState { enum UIState {
UI_STATE_NORMAL, // Normal UI (icon + label) UI_STATE_NORMAL, // Normal UI (icon + label)
...@@ -223,6 +228,7 @@ class APP_LIST_EXPORT AppListItemView : public views::Button, ...@@ -223,6 +228,7 @@ class APP_LIST_EXPORT AppListItemView : public views::Button,
// AppListItemObserver overrides: // AppListItemObserver overrides:
void ItemIconChanged(AppListConfigType config_type) override; void ItemIconChanged(AppListConfigType config_type) override;
void ItemNameChanged() override; void ItemNameChanged() override;
void ItemBadgeVisibilityChanged(bool is_badge_visible) override;
void ItemBeingDestroyed() override; void ItemBeingDestroyed() override;
// ui::ImplicitAnimationObserver: // ui::ImplicitAnimationObserver:
...@@ -298,6 +304,13 @@ class APP_LIST_EXPORT AppListItemView : public views::Button, ...@@ -298,6 +304,13 @@ class APP_LIST_EXPORT AppListItemView : public views::Button,
// The scaling factor for displaying the app icon. // The scaling factor for displaying the app icon.
float icon_scale_ = 1.0f; float icon_scale_ = 1.0f;
// Draws an indicator in the top right corner of the image to represent an
// active notification.
AppNotificationIndicatorView* notification_indicator_ = nullptr;
// Whether the notification indicator flag is enabled.
const bool is_notification_indicator_enabled_;
base::WeakPtrFactory<AppListItemView> weak_ptr_factory_{this}; base::WeakPtrFactory<AppListItemView> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(AppListItemView); DISALLOW_COPY_AND_ASSIGN(AppListItemView);
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/app_list/views/app_list_item_view.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/app_list/test/app_list_test_view_delegate.h"
#include "ash/app_list/views/app_list_main_view.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/app_list/views/apps_container_view.h"
#include "ash/app_list/views/contents_view.h"
#include "base/test/scoped_feature_list.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/test/views_test_base.h"
namespace ash {
class AppListItemViewTest : public views::ViewsTestBase {
public:
AppListItemViewTest() {
scoped_feature_list_.InitWithFeatures({features::kNotificationIndicator},
{});
}
~AppListItemViewTest() override = default;
// views::ViewsTestBase:
void SetUp() override {
views::ViewsTestBase::SetUp();
delegate_ = std::make_unique<test::AppListTestViewDelegate>();
app_list_view_ = new AppListView(delegate_.get());
app_list_view_->InitView(GetContext());
}
AppListItemView* CreateAppListItemView() {
AppsGridView* apps_grid_view = app_list_view_->app_list_main_view()
->contents_view()
->apps_container_view()
->apps_grid_view();
return new AppListItemView(apps_grid_view, new AppListItem("Item 0"),
delegate_.get(), false);
}
private:
AppListView* app_list_view_ = nullptr;
std::unique_ptr<test::AppListTestViewDelegate> delegate_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// Test that the notification indicator has a color which is calculated
// correctly when an icon is set for the AppListItemView.
TEST_F(AppListItemViewTest, NotificatonBadgeColor) {
AppListItemView* view = CreateAppListItemView();
const int width = 64;
const int height = 64;
SkBitmap all_black_icon;
all_black_icon.allocN32Pixels(width, height);
all_black_icon.eraseColor(SK_ColorBLACK);
view->SetIcon(gfx::ImageSkia::CreateFrom1xBitmap(all_black_icon));
// For an all black icon, a white notification badge is expected, since there
// is no other light vibrant color to get from the icon.
EXPECT_EQ(SK_ColorWHITE, view->GetNotificationIndicatorColorForTest());
// Create an icon that is half kGoogleRed300 and half kGoogleRed600.
SkBitmap red_icon;
red_icon.allocN32Pixels(width, height);
red_icon.eraseColor(gfx::kGoogleRed300);
red_icon.erase(gfx::kGoogleRed600, {0, 0, width, height / 2});
view->SetIcon(gfx::ImageSkia::CreateFrom1xBitmap(red_icon));
// For the red icon, the notification badge should calculate and use the
// kGoogleRed300 color as the light vibrant color taken from the icon.
EXPECT_EQ(gfx::kGoogleRed300, view->GetNotificationIndicatorColorForTest());
}
} // namespace ash
...@@ -1428,8 +1428,8 @@ const char kTextureLayerSkipWaitForActivationDescription[] = ...@@ -1428,8 +1428,8 @@ const char kTextureLayerSkipWaitForActivationDescription[] =
const char kNotificationIndicatorName[] = "Notification Indicators"; const char kNotificationIndicatorName[] = "Notification Indicators";
const char kNotificationIndicatorDescription[] = const char kNotificationIndicatorDescription[] =
"Enable notification indicators, which appear on shelf app icons when a " "Enable notification indicators, which appear on shelf app icons and "
"notification is active."; "launcher apps when a notification is active.";
const char kNotificationSchedulerDebugOptionName[] = const char kNotificationSchedulerDebugOptionName[] =
"Notification scheduler debug options"; "Notification scheduler debug options";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment