0

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:
James Cook
2022-12-12 21:50:56 +00:00
committed by Chromium LUCI CQ
parent 2b67003184
commit ecb44fd7bf
12 changed files with 157 additions and 6 deletions

@ -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

@ -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

@ -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