Add zero state view for quick settings cast detailed page (QsRevamp)
When there are no Cast devices visible on the network, show a new "zero state" view after clicking the Cast feature tile. https://screenshot.googleplex.com/3gELSoGNZyniGFK https://screenshot.googleplex.com/ApyP3Qo6CQNpq34 The image is a temporary placeholder PNG. Later UX will provide us with art assets that adapt to the dark/light/Jelly theme. Bug: b:252872586 Test: added to ash_unittests Change-Id: I0b1f807034b75982111c31c07a362e03eeed40be Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4093736 Reviewed-by: Alex Newcomer <newcomer@chromium.org> Commit-Queue: James Cook <jamescook@chromium.org> Cr-Commit-Position: refs/heads/main@{#1082032}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
2b67003184
commit
ecb44fd7bf
ash
BUILD.gnash_strings.grd
ash_strings_grd
IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE.png.sha1IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE.png.sha1
public
cpp
resources
system
@ -1204,6 +1204,8 @@ component("ash") {
|
||||
"system/cast/cast_feature_pod_controller.h",
|
||||
"system/cast/cast_notification_controller.cc",
|
||||
"system/cast/cast_notification_controller.h",
|
||||
"system/cast/cast_zero_state_view.cc",
|
||||
"system/cast/cast_zero_state_view.h",
|
||||
"system/cast/tray_cast.cc",
|
||||
"system/cast/tray_cast.h",
|
||||
"system/cast/unified_cast_detailed_view_controller.cc",
|
||||
|
@ -492,6 +492,12 @@ Style notes:
|
||||
<message name="IDS_ASH_STATUS_TRAY_CAST_ACCESS_CODE_CAST_CONNECT" desc="Menu item label that, when clicked on, opens the access code casting dialog.">
|
||||
Connect with a code
|
||||
</message>
|
||||
<message name="IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE" desc="The main label for the system tray Cast view when no Chromecast devices are available.">
|
||||
Cast your screen from your Chromebook
|
||||
</message>
|
||||
<message name="IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE" desc="The secondary label for the system tray Cast view when no Chromecast devices are available.">
|
||||
Make sure your Chromebook is on the same Wi-Fi network as your Chromecast device.
|
||||
</message>
|
||||
<message name="IDS_ASH_STATUS_TRAY_DARK_THEME" desc="The label used as the header in the dark theme popup. [CHAR_LIMIT=19">
|
||||
Dark theme
|
||||
</message>
|
||||
|
@ -0,0 +1 @@
|
||||
ba9e1fd2ba582ae131af0268b7e356fb5aadd0f6
|
@ -0,0 +1 @@
|
||||
ba9e1fd2ba582ae131af0268b7e356fb5aadd0f6
|
@ -29,6 +29,9 @@
|
||||
<include name="IDR_SETTINGS_RGB_KEYBOARD_RAINBOW_COLOR_48_PNG" file="unscaled_resources/rgb_keyboard_rainbow_color_48.png" type="BINDATA" />
|
||||
<!-- Accessibility animations -->
|
||||
<include name="IDR_DICTATION_BUBBLE_ANIMATION" file="unscaled_resources/dictation_bubble_animation.json" type="BINDATA"/>
|
||||
<!-- System tray Cast images -->
|
||||
<include name="IDR_TRAY_CAST_ZERO_STATE_DARK" file="unscaled_resources/tray_cast_zero_state_dark.png" type="BINDATA" />
|
||||
<include name="IDR_TRAY_CAST_ZERO_STATE_LIGHT" file="unscaled_resources/tray_cast_zero_state_light.png" type="BINDATA" />
|
||||
</includes>
|
||||
<structures>
|
||||
<structure type="lottie" name="IDR_PHONE_HUB_ONBOARDING_IMAGE" file="unscaled_resources/phone_hub_onboarding_image.json" compress="gzip" />
|
||||
|
Binary file not shown.
After ![]() (image error) Size: 21 KiB |
Binary file not shown.
After ![]() (image error) Size: 20 KiB |
67
ash/system/cast/cast_zero_state_view.cc
Normal file
67
ash/system/cast/cast_zero_state_view.cc
Normal file
@ -0,0 +1,67 @@
|
||||
// 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.
|
||||
|
||||
#include "ash/system/cast/cast_zero_state_view.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ash/bubble/bubble_utils.h"
|
||||
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
|
||||
#include "ash/public/cpp/style/dark_light_mode_controller.h"
|
||||
#include "ash/strings/grit/ash_strings.h"
|
||||
#include "ash/style/rounded_container.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/models/image_model.h"
|
||||
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
|
||||
#include "ui/views/border.h"
|
||||
#include "ui/views/controls/image_view.h"
|
||||
#include "ui/views/controls/label.h"
|
||||
#include "ui/views/layout/box_layout.h"
|
||||
#include "ui/views/view_class_properties.h"
|
||||
|
||||
using views::BoxLayout;
|
||||
using views::ImageView;
|
||||
using views::Label;
|
||||
|
||||
namespace ash {
|
||||
|
||||
CastZeroStateView::CastZeroStateView() {
|
||||
SetUseDefaultFillLayout(true);
|
||||
SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(0, 16, 16, 16)));
|
||||
|
||||
// The zero-state view are inside a rounded container.
|
||||
auto* container = AddChildView(std::make_unique<RoundedContainer>());
|
||||
container->SetBorderInsets(gfx::Insets::VH(0, 32));
|
||||
|
||||
// The views are centered vertically.
|
||||
std::unique_ptr<BoxLayout> layout =
|
||||
std::make_unique<BoxLayout>(BoxLayout::Orientation::kVertical);
|
||||
layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kCenter);
|
||||
container->SetLayoutManager(std::move(layout));
|
||||
|
||||
// TODO(b/252872586): UX provided unscaled PNG images as placeholders. They
|
||||
// should be replaced with a themed vector icon.
|
||||
int image_resource_id = DarkLightModeController::Get()->IsDarkModeEnabled()
|
||||
? IDR_TRAY_CAST_ZERO_STATE_DARK
|
||||
: IDR_TRAY_CAST_ZERO_STATE_LIGHT;
|
||||
ImageView* image = container->AddChildView(std::make_unique<ImageView>(
|
||||
ui::ImageModel::FromResourceId(image_resource_id)));
|
||||
// The placeholders are unscaled (2x), so handle sizing here.
|
||||
image->SetImageSize(gfx::Size(176, 170));
|
||||
|
||||
Label* title = container->AddChildView(std::make_unique<Label>(
|
||||
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_TITLE)));
|
||||
bubble_utils::ApplyStyle(title, bubble_utils::TypographyStyle::kTitle1);
|
||||
title->SetEnabledColorId(cros_tokens::kCrosSysOnSurface);
|
||||
title->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(32, 0, 0, 0));
|
||||
|
||||
Label* subtitle = container->AddChildView(std::make_unique<Label>(
|
||||
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_ZERO_STATE_SUBTITLE)));
|
||||
subtitle->SetMultiLine(true);
|
||||
bubble_utils::ApplyStyle(subtitle, bubble_utils::TypographyStyle::kBody1);
|
||||
subtitle->SetEnabledColorId(cros_tokens::kTextColorSecondary);
|
||||
subtitle->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(8, 0, 0, 0));
|
||||
}
|
||||
|
||||
} // namespace ash
|
23
ash/system/cast/cast_zero_state_view.h
Normal file
23
ash/system/cast/cast_zero_state_view.h
Normal file
@ -0,0 +1,23 @@
|
||||
// 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_CAST_CAST_ZERO_STATE_VIEW_H_
|
||||
#define ASH_SYSTEM_CAST_CAST_ZERO_STATE_VIEW_H_
|
||||
|
||||
#include "ui/views/view.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
// The view shown in the system tray when there are no cast targets available.
|
||||
class CastZeroStateView : public views::View {
|
||||
public:
|
||||
CastZeroStateView();
|
||||
CastZeroStateView(const CastZeroStateView&) = delete;
|
||||
CastZeroStateView& operator=(const CastZeroStateView&) = delete;
|
||||
~CastZeroStateView() override = default;
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
||||
#endif // ASH_SYSTEM_CAST_CAST_ZERO_STATE_VIEW_H_
|
@ -10,15 +10,14 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ash/constants/ash_features.h"
|
||||
#include "ash/public/cpp/ash_view_ids.h"
|
||||
#include "ash/public/cpp/system_tray_client.h"
|
||||
#include "ash/resources/vector_icons/vector_icons.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/strings/grit/ash_strings.h"
|
||||
#include "ash/style/rounded_container.h"
|
||||
#include "ash/system/cast/cast_zero_state_view.h"
|
||||
#include "ash/system/model/system_tray_model.h"
|
||||
#include "ash/system/tray/hover_highlight_view.h"
|
||||
#include "ash/system/tray/tray_constants.h"
|
||||
#include "ash/system/tray/tray_detailed_view.h"
|
||||
#include "base/metrics/user_metrics.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
@ -27,12 +26,9 @@
|
||||
#include "components/vector_icons/vector_icons.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
#include "ui/base/resource/resource_bundle.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/gfx/paint_vector_icon.h"
|
||||
#include "ui/gfx/vector_icon_types.h"
|
||||
#include "ui/views/controls/button/button.h"
|
||||
#include "ui/views/controls/scroll_view.h"
|
||||
#include "ui/views/layout/box_layout.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
@ -112,6 +108,11 @@ void CastDetailedView::UpdateReceiverListFromCachedData() {
|
||||
// Remove all of the existing views.
|
||||
view_to_sink_map_.clear();
|
||||
scroll_content()->RemoveAllChildViews();
|
||||
add_access_code_device_ = nullptr;
|
||||
if (zero_state_view_) {
|
||||
RemoveChildViewT(zero_state_view_);
|
||||
zero_state_view_ = nullptr;
|
||||
}
|
||||
|
||||
// QsRevamp places items in a rounded container.
|
||||
views::View* item_container =
|
||||
@ -137,10 +138,28 @@ void CastDetailedView::UpdateReceiverListFromCachedData() {
|
||||
view_to_sink_map_[container] = sink.id;
|
||||
}
|
||||
|
||||
// If there are no receiver views, show the zero state view.
|
||||
if (features::IsQsRevampEnabled() && !add_access_code_device_ &&
|
||||
view_to_sink_map_.empty()) {
|
||||
AddZeroStateView();
|
||||
scroller()->SetVisible(false);
|
||||
} else {
|
||||
scroller()->SetVisible(true);
|
||||
}
|
||||
|
||||
scroll_content()->SizeToPreferredSize();
|
||||
scroller()->Layout();
|
||||
}
|
||||
|
||||
void CastDetailedView::AddZeroStateView() {
|
||||
DCHECK(!zero_state_view_);
|
||||
DCHECK(scroller());
|
||||
zero_state_view_ = AddChildViewAt(std::make_unique<CastZeroStateView>(),
|
||||
GetIndexOf(scroller()).value());
|
||||
// Make the view fill the entire space below the title row.
|
||||
box_layout()->SetFlexForView(zero_state_view_, 1);
|
||||
}
|
||||
|
||||
void CastDetailedView::HandleViewClicked(views::View* view) {
|
||||
// Find the receiver we are going to cast to.
|
||||
auto it = view_to_sink_map_.find(view);
|
||||
|
@ -13,6 +13,10 @@
|
||||
#include "ash/system/tray/tray_detailed_view.h"
|
||||
#include "ui/base/metadata/metadata_header_macros.h"
|
||||
|
||||
namespace views {
|
||||
class View;
|
||||
} // namespace views
|
||||
|
||||
namespace ash {
|
||||
|
||||
// This view displays a list of cast receivers that can be clicked on and casted
|
||||
@ -44,6 +48,9 @@ class ASH_EXPORT CastDetailedView : public TrayDetailedView,
|
||||
|
||||
void UpdateReceiverListFromCachedData();
|
||||
|
||||
// Adds the view shown when no cast devices are available (with QsRevamp).
|
||||
void AddZeroStateView();
|
||||
|
||||
// TrayDetailedView:
|
||||
void HandleViewClicked(views::View* view) override;
|
||||
|
||||
@ -55,6 +62,9 @@ class ASH_EXPORT CastDetailedView : public TrayDetailedView,
|
||||
|
||||
// Special list item that, if clicked, launches the access code casting dialog
|
||||
views::View* add_access_code_device_ = nullptr;
|
||||
|
||||
// View shown when no cast devices are available (with QsRevamp).
|
||||
views::View* zero_state_view_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
@ -80,6 +80,8 @@ class CastDetailedViewTest : public AshTestBase {
|
||||
return views;
|
||||
}
|
||||
|
||||
views::View* GetZeroStateView() { return detailed_view_->zero_state_view_; }
|
||||
|
||||
// Adds two simulated cast devices.
|
||||
void AddCastDevices() {
|
||||
std::vector<SinkAndRoute> devices;
|
||||
@ -98,6 +100,9 @@ class CastDetailedViewTest : public AshTestBase {
|
||||
detailed_view_->OnDevicesUpdated(devices);
|
||||
}
|
||||
|
||||
// Removes simulated cast devices.
|
||||
void ResetCastDevices() { detailed_view_->OnDevicesUpdated({}); }
|
||||
|
||||
base::test::ScopedFeatureList feature_list_;
|
||||
std::unique_ptr<views::Widget> widget_;
|
||||
TestCastConfigController cast_config_;
|
||||
@ -128,4 +133,18 @@ TEST_F(CastDetailedViewTest, ClickOnViewClosesBubble) {
|
||||
EXPECT_EQ(delegate_->close_bubble_call_count(), 1u);
|
||||
}
|
||||
|
||||
TEST_F(CastDetailedViewTest, ZeroStateView) {
|
||||
// The zero state view shows when there are no cast devices.
|
||||
ASSERT_TRUE(GetDeviceViews().empty());
|
||||
EXPECT_TRUE(GetZeroStateView());
|
||||
|
||||
// Adding cast devices hides the zero state view.
|
||||
AddCastDevices();
|
||||
EXPECT_FALSE(GetZeroStateView());
|
||||
|
||||
// Removing cast devices shows the zero state view.
|
||||
ResetCastDevices();
|
||||
EXPECT_TRUE(GetZeroStateView());
|
||||
}
|
||||
|
||||
} // namespace ash
|
||||
|
Reference in New Issue
Block a user