#include "ash/system/phonehub/app_stream_launcher_view.h"
#include <cmath>
#include <memory>
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/controls/rounded_scroll_bar.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/style_util.h"
#include "ash/style/typography.h"
#include "ash/system/phonehub/app_stream_launcher_item.h"
#include "ash/system/phonehub/app_stream_launcher_list_item.h"
#include "ash/system/phonehub/app_stream_launcher_view.h"
#include "ash/system/phonehub/phone_hub_view_ids.h"
#include "ash/system/phonehub/ui_constants.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h"
#include "chromeos/ash/components/phonehub/notification.h"
#include "chromeos/ash/components/phonehub/phone_hub_manager.h"
#include "chromeos/ash/components/phonehub/user_action_recorder.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/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/view.h"
namespace ash {
namespace {
constexpr auto kVerticalScrollInsets = gfx::Insets::TLBR(1, 0, 1, 1);
constexpr auto kHeaderDefaultSpacing = gfx::Insets::VH(0, 0);
constexpr gfx::Size kDefaultAppListScrollViewSize = gfx::Size(400, 400);
constexpr auto kHeaderViewInsets = gfx::Insets::TLBR(25, 15, 15, 15);
constexpr int kHeaderChildrenSpacing = 20;
constexpr int kVerticalPaddingBetweenSections = 16;
constexpr int kAppListItemHorizontalMargin = 16;
constexpr int kAppListItemSpacing = 8;
}
AppStreamLauncherView::AppStreamLauncherView(
phonehub::PhoneHubManager* phone_hub_manager)
: phone_hub_manager_(phone_hub_manager) {
SetID(PhoneHubViewID::kAppStreamLauncherView);
auto* layout_manager =
SetLayoutManager(std::make_unique<views::FlexLayout>());
layout_manager->SetInteriorMargin(gfx::Insets::VH(0, 0))
.SetOrientation(views::LayoutOrientation::kVertical)
.SetCollapseMargins(false)
.SetDefault(views::kMarginsKey, kHeaderDefaultSpacing)
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
AddChildView(CreateHeaderView());
auto* app_list_view = AddChildView(CreateAppListView());
gfx::Size launcher_size;
if (phone_hub_manager->GetAppStreamLauncherDataModel() &&
phone_hub_manager->GetAppStreamLauncherDataModel()->launcher_height() >
kDefaultAppListScrollViewSize.height()) {
launcher_size = gfx::Size(
phone_hub_manager->GetAppStreamLauncherDataModel()->launcher_width(),
phone_hub_manager->GetAppStreamLauncherDataModel()->launcher_height());
} else {
launcher_size = kDefaultAppListScrollViewSize;
}
app_list_view->SetPreferredSize(launcher_size);
app_list_view->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kPreferred,
false)
.WithWeight(1));
phone_hub_manager->GetUserActionRecorder()->RecordUiOpened();
UpdateFromDataModel();
if (phone_hub_manager->GetAppStreamLauncherDataModel())
phone_hub_manager->GetAppStreamLauncherDataModel()->AddObserver(this);
}
AppStreamLauncherView::~AppStreamLauncherView() {
if (phone_hub_manager_->GetAppStreamLauncherDataModel())
phone_hub_manager_->GetAppStreamLauncherDataModel()->RemoveObserver(this);
}
std::unique_ptr<views::View> AppStreamLauncherView::CreateAppListView() {
auto scroll_view = std::make_unique<views::ScrollView>(
views::ScrollView::ScrollWithLayers::kEnabled);
scroll_view->ClipHeightTo(0, std::numeric_limits<int>::max());
scroll_view->SetDrawOverflowIndicator(false);
scroll_view->SetBackgroundColor(std::nullopt);
scroll_view->SetAllowKeyboardScrolling(false);
scroll_view->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
scroll_view->SetHorizontalScrollBarMode(
views::ScrollView::ScrollBarMode::kDisabled);
auto vertical_scroll = std::make_unique<RoundedScrollBar>(
views::ScrollBar::Orientation::kVertical);
vertical_scroll->SetInsets(kVerticalScrollInsets);
vertical_scroll->SetSnapBackOnDragOutside(false);
scroll_view->SetVerticalScrollBar(std::move(vertical_scroll));
auto scroll_contents = std::make_unique<views::View>();
scroll_contents->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded,
false)
.WithWeight(1));
auto* layout =
scroll_contents->SetLayoutManager(std::make_unique<views::FlexLayout>());
layout->SetOrientation(views::LayoutOrientation::kVertical)
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
layout->SetInteriorMargin(gfx::Insets::VH(kVerticalPaddingBetweenSections,
kAppListItemHorizontalMargin));
items_container_ =
scroll_contents->AddChildView(std::make_unique<views::View>());
items_container_->SetPaintToLayer();
items_container_->layer()->SetFillsBoundsOpaquely(false);
scroll_view->SetContents(std::move(scroll_contents));
return scroll_view;
}
void AppStreamLauncherView::AppIconActivated(
phonehub::Notification::AppMetadata app) {
auto* interaction_handler_ =
phone_hub_manager_->GetRecentAppsInteractionHandler();
if (!interaction_handler_)
return;
interaction_handler_->NotifyRecentAppClicked(
app, eche_app::mojom::AppStreamLaunchEntryPoint::APPS_LIST);
}
void AppStreamLauncherView::UpdateFromDataModel() {
if (!items_container_)
return;
items_container_->RemoveAllChildViews();
if (!phone_hub_manager_->GetAppStreamLauncherDataModel())
return;
const std::vector<phonehub::Notification::AppMetadata>* apps_list =
phone_hub_manager_->GetAppStreamLauncherDataModel()
->GetAppsListSortedByName();
CreateListView(apps_list);
}
std::unique_ptr<views::View> AppStreamLauncherView::CreateListItemView(
const phonehub::Notification::AppMetadata& app) {
return std::make_unique<AppStreamLauncherListItem>(
base::BindRepeating(&AppStreamLauncherView::AppIconActivated,
base::Unretained(this), app),
app);
}
std::unique_ptr<views::View> AppStreamLauncherView::CreateHeaderView() {
auto header = std::make_unique<views::View>();
header->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, kHeaderViewInsets,
kHeaderChildrenSpacing));
header->SetBackground(
views::CreateSolidBackground(kColorAshControlBackgroundColorInactive));
arrow_back_button_ = header->AddChildView(CreateButton(
base::BindRepeating(&AppStreamLauncherView::OnArrowBackActivated,
weak_factory_.GetWeakPtr()),
kEcheArrowBackIcon, IDS_APP_ACCNAME_BACK));
views::Label* title = header->AddChildView(std::make_unique<views::Label>(
std::u16string(), views::style::CONTEXT_DIALOG_TITLE,
views::style::STYLE_PRIMARY,
gfx::DirectionalityMode::DIRECTIONALITY_AS_URL));
title->SetMultiLine(true);
title->SetAllowCharacterBreak(true);
title->SetProperty(views::kBoxLayoutFlexKey,
views::BoxLayoutFlexSpecification());
title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
title->SetText(
l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_APP_STREAM_LAUNCHER_TITLE));
TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosHeadline1,
*title);
return header;
}
std::unique_ptr<views::Button> AppStreamLauncherView::CreateButton(
views::Button::PressedCallback callback,
const gfx::VectorIcon& icon,
int message_id) {
SkColor color =
AshColorProvider::Get()->GetColor(cros_tokens::kIconColorPrimary);
SkColor disabled_color = SkColorSetA(color, gfx::kDisabledControlAlpha);
auto button = views::CreateVectorImageButton(std::move(callback));
views::SetImageFromVectorIconWithColor(button.get(), icon, color,
disabled_color);
ash::StyleUtil::SetUpInkDropForButton(button.get(), gfx::Insets(),
false,
true);
views::FocusRing::Get(button.get())
->SetColorId(static_cast<ui::ColorId>(cros_tokens::kCrosSysFocusRing));
button->SetTooltipText(l10n_util::GetStringUTF16(message_id));
button->SizeToPreferredSize();
views::InstallCircleHighlightPathGenerator(button.get());
return button;
}
void AppStreamLauncherView::OnArrowBackActivated() {
phone_hub_manager_->GetAppStreamLauncherDataModel()
->SetShouldShowMiniLauncher(false);
}
void AppStreamLauncherView::ChildPreferredSizeChanged(View* child) {
PreferredSizeChanged();
}
void AppStreamLauncherView::ChildVisibilityChanged(View* child) {
PreferredSizeChanged();
}
phone_hub_metrics::Screen AppStreamLauncherView::GetScreenForMetrics() const {
return phone_hub_metrics::Screen::kMiniLauncher;
}
void AppStreamLauncherView::OnBubbleClose() {
RemoveAllChildViews();
}
void AppStreamLauncherView::OnAppListChanged() {
if (features::IsEcheSWAEnabled()) {
UpdateFromDataModel();
}
}
void AppStreamLauncherView::CreateListView(
const std::vector<phonehub::Notification::AppMetadata>* apps_list) {
items_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets::VH(0, 0),
kAppListItemSpacing));
for (auto& app : *apps_list) {
items_container_->AddChildView(CreateListItemView(app));
}
}
BEGIN_METADATA(AppStreamLauncherView)
END_METADATA
}