blob: c4a982987a82ee195dde2bf610cc74b798c55ef1 [file] [log] [blame]
// Copyright 2014 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/search_result_tile_item_list_view.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include "ash/app_list/app_list_util.h"
#include "ash/app_list/app_list_view_delegate.h"
#include "ash/app_list/model/search/search_result.h"
#include "ash/app_list/views/search_result_page_view.h"
#include "ash/app_list/views/search_result_tile_item_view.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/app_list/internal_app_id_constants.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/i18n/rtl.h"
#include "base/stl_util.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/box_layout.h"
namespace {
// Layout constants used when fullscreen app list feature is enabled.
constexpr size_t kMaxNumSearchResultTiles = 6;
constexpr int kItemListVerticalSpacing = 16;
constexpr int kItemListHorizontalSpacing = 12;
constexpr int kBetweenItemSpacing = 8;
constexpr int kSeparatorLeftRightPadding = 4;
constexpr int kSeparatorHeight = 46;
constexpr int kSeparatorTopPadding = 10;
constexpr SkColor kSeparatorColor = SkColorSetA(gfx::kGoogleGrey900, 0x24);
} // namespace
namespace app_list {
SearchResultTileItemListView::SearchResultTileItemListView(
SearchResultPageView* search_result_page_view,
views::Textfield* search_box,
AppListViewDelegate* view_delegate)
: SearchResultContainerView(view_delegate),
search_result_page_view_(search_result_page_view),
search_box_(search_box),
is_play_store_app_search_enabled_(
app_list_features::IsPlayStoreAppSearchEnabled()),
is_app_reinstall_recommendation_enabled_(
app_list_features::IsAppReinstallZeroStateEnabled()) {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal,
gfx::Insets(kItemListVerticalSpacing, kItemListHorizontalSpacing),
kBetweenItemSpacing));
for (size_t i = 0; i < kMaxNumSearchResultTiles; ++i) {
if (is_app_reinstall_recommendation_enabled_ ||
is_play_store_app_search_enabled_) {
views::Separator* separator = new views::Separator;
separator->SetVisible(false);
separator->SetBorder(views::CreateEmptyBorder(
kSeparatorTopPadding, kSeparatorLeftRightPadding,
AppListConfig::instance().search_tile_height() - kSeparatorHeight,
kSeparatorLeftRightPadding));
separator->SetColor(kSeparatorColor);
separator_views_.push_back(separator);
AddChildView(separator);
}
SearchResultTileItemView* tile_item = new SearchResultTileItemView(
view_delegate, nullptr /* pagination model */,
false /* show_in_apps_page */);
tile_item->SetParentBackgroundColor(
AppListConfig::instance().card_background_color());
tile_views_.push_back(tile_item);
AddChildView(tile_item);
AddObservedResultView(tile_item);
}
}
SearchResultTileItemListView::~SearchResultTileItemListView() = default;
void SearchResultTileItemListView::NotifyFirstResultYIndex(int y_index) {
for (size_t i = 0; i < static_cast<size_t>(num_results()); ++i)
tile_views_[i]->result()->set_distance_from_origin(i + y_index);
}
int SearchResultTileItemListView::GetYSize() {
return num_results() ? 1 : 0;
}
SearchResultBaseView* SearchResultTileItemListView::GetFirstResultView() {
DCHECK(!tile_views_.empty());
return num_results() <= 0 ? nullptr : tile_views_[0];
}
int SearchResultTileItemListView::DoUpdate() {
std::vector<SearchResult*> display_results = GetDisplayResults();
SearchResult::ResultType previous_type = ash::SearchResultType::kUnknown;
ash::SearchResultDisplayType previous_display_type =
ash::SearchResultDisplayType::kNone;
std::set<std::string> result_id_removed, result_id_added;
for (size_t i = 0; i < kMaxNumSearchResultTiles; ++i) {
// If the current result at i exists, wants to be notified and is a
// different id, notify it that it is being hidden.
SearchResult* current_result = tile_views_[i]->result();
if (current_result != nullptr) {
result_id_removed.insert(current_result->id());
}
if (i >= display_results.size()) {
if (is_app_reinstall_recommendation_enabled_ ||
is_play_store_app_search_enabled_) {
separator_views_[i]->SetVisible(false);
}
tile_views_[i]->SetResult(nullptr);
continue;
}
SearchResult* item = display_results[i];
tile_views_[i]->SetResult(item);
result_id_added.insert(item->id());
if (is_play_store_app_search_enabled_ ||
is_app_reinstall_recommendation_enabled_) {
if (i > 0 && (item->result_type() != previous_type ||
item->display_type() != previous_display_type)) {
// Add a separator to separate search results of different types.
// The strategy here is to only add a separator only if current search
// result type is different from the previous one. The strategy is
// based on the assumption that the search results are already
// separated in groups based on their result types.
separator_views_[i]->SetVisible(true);
} else {
separator_views_[i]->SetVisible(false);
}
}
previous_type = item->result_type();
previous_display_type = item->display_type();
}
// notify visibility changes, if needed.
std::set<std::string> actual_added_ids =
base::STLSetDifference<std::set<std::string>>(result_id_added,
result_id_removed);
for (const std::string& added_id : actual_added_ids) {
SearchResult* added =
view_delegate()->GetSearchModel()->FindSearchResult(added_id);
if (added != nullptr && added->notify_visibility_change()) {
view_delegate()->OnSearchResultVisibilityChanged(added->id(), shown());
}
}
if (shown() != false) {
std::set<std::string> actual_removed_ids =
base::STLSetDifference<std::set<std::string>>(result_id_removed,
result_id_added);
// we only notify removed items if we're in the middle of showing.
for (const std::string& removed_id : actual_removed_ids) {
SearchResult* removed =
view_delegate()->GetSearchModel()->FindSearchResult(removed_id);
if (removed != nullptr && removed->notify_visibility_change()) {
view_delegate()->OnSearchResultVisibilityChanged(removed->id(),
false /*=shown*/);
}
}
}
set_container_score(
display_results.empty() ? 0 : display_results.front()->display_score());
return display_results.size();
}
std::vector<SearchResult*> SearchResultTileItemListView::GetDisplayResults() {
base::string16 raw_query = search_box_->text();
base::string16 query;
base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
// We ask for kMaxNumSearchResultTiles total results, and we prefer reinstall
// candidates if appropriate. we fetch |reinstall_results| first, and
// front-fill the rest from the regular result types.
auto reinstall_filter =
base::BindRepeating([](const SearchResult& r) -> bool {
return r.display_type() ==
ash::SearchResultDisplayType::kRecommendation &&
r.result_type() == ash::SearchResultType::kPlayStoreReinstallApp;
});
std::vector<SearchResult*> reinstall_results =
is_app_reinstall_recommendation_enabled_ && query.empty()
? SearchModel::FilterSearchResultsByFunction(
results(), reinstall_filter, kMaxNumSearchResultTiles)
: std::vector<SearchResult*>();
SearchResult::DisplayType display_type =
app_list_features::IsZeroStateSuggestionsEnabled()
? (query.empty() ? ash::SearchResultDisplayType::kRecommendation
: ash::SearchResultDisplayType::kTile)
: ash::SearchResultDisplayType::kTile;
size_t display_num = kMaxNumSearchResultTiles - reinstall_results.size();
// Do not display the continue reading app in the search result list.
auto non_reinstall_filter = base::BindRepeating(
[](const SearchResult::DisplayType& display_type,
const SearchResult& r) -> bool {
return r.display_type() == display_type &&
r.result_type() !=
ash::SearchResultType::kPlayStoreReinstallApp &&
r.id() != app_list::kInternalAppIdContinueReading;
},
display_type);
std::vector<SearchResult*> display_results =
SearchModel::FilterSearchResultsByFunction(
results(), non_reinstall_filter, display_num);
// Append the reinstalls to the display results.
display_results.insert(display_results.end(), reinstall_results.begin(),
reinstall_results.end());
return display_results;
}
bool SearchResultTileItemListView::OnKeyPressed(const ui::KeyEvent& event) {
// Let the FocusManager handle Left/Right keys.
if (!IsUnhandledUpDownKeyEvent(event))
return false;
views::View* next_focusable_view = nullptr;
// Since search result tile item views have horizontal layout, hitting
// up/down when one of them is focused moves focus to the previous/next
// search result container.
if (event.key_code() == ui::VKEY_UP) {
next_focusable_view = GetFocusManager()->GetNextFocusableView(
tile_views_.front(), GetWidget(), true, false);
if (!search_result_page_view_->Contains(next_focusable_view)) {
// Focus should be moved to search box when it is moved outside search
// result page view.
search_box_->RequestFocus();
return true;
}
} else {
DCHECK_EQ(event.key_code(), ui::VKEY_DOWN);
next_focusable_view = GetFocusManager()->GetNextFocusableView(
tile_views_.back(), GetWidget(), false, false);
}
if (next_focusable_view) {
next_focusable_view->RequestFocus();
return true;
}
// Return false to let FocusManager to handle default focus move by key
// events.
return false;
}
const char* SearchResultTileItemListView::GetClassName() const {
return "SearchResultTileItemListView";
}
void SearchResultTileItemListView::OnShownChanged() {
SearchResultContainerView::OnShownChanged();
for (const auto* tile_view : tile_views_) {
SearchResult* result = tile_view->result();
if (result == nullptr) {
continue;
}
if (result->notify_visibility_change()) {
view_delegate()->OnSearchResultVisibilityChanged(result->id(), shown());
}
}
}
void SearchResultTileItemListView::VisibilityChanged(View* starting_from,
bool is_visible) {
SearchResultContainerView::VisibilityChanged(starting_from, is_visible);
// We only do this work when is_visible is false, since this is how we
// receive the event. We filter and only run when shown.
if (is_visible && shown()) {
return;
}
for (const auto* tile_view : tile_views_) {
SearchResult* result = tile_view->result();
if (result == nullptr) {
continue;
}
if (result->notify_visibility_change()) {
view_delegate()->OnSearchResultVisibilityChanged(result->id(),
false /*=visible*/);
}
}
}
} // namespace app_list