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,7 +50,8 @@ class AppListControllerObserver; ...@@ -49,7 +50,8 @@ 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 AppListController,
public SessionObserver, public SessionObserver,
public AppListModelObserver, public AppListModelObserver,
public AppListViewDelegate, public AppListViewDelegate,
...@@ -64,7 +66,8 @@ class ASH_EXPORT AppListControllerImpl : public AppListController, ...@@ -64,7 +66,8 @@ class ASH_EXPORT AppListControllerImpl : public AppListController,
public MruWindowTracker::Observer, public MruWindowTracker::Observer,
public AssistantControllerObserver, public AssistantControllerObserver,
public AssistantUiModelObserver, public AssistantUiModelObserver,
public HomeScreenDelegate { 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