Skip to content

Commit

Permalink
[Content Previews] Multiple Images
Browse files Browse the repository at this point in the history
Add support for loading up to 4 images at a time. Images are
arranged in a grid as per the screenshots. When there are more
than 4 images we replace the bottom right grid with a number
representing how many more images there are.

Screenshots:
1 image https://screenshot.googleplex.com/4NWrscyFdjMC7eL
2 images https://screenshot.googleplex.com/4pGTSf3PhQsTZBy
3 images https://screenshot.googleplex.com/hFR89A5F6d9UvqY
4 images https://screenshot.googleplex.com/9yc5kuZYM2pMq2d
4+ images https://screenshot.googleplex.com/uBwcMpSpMLj5dLY
10+ images https://screenshot.googleplex.com/Aw4WmurPowzzggm
Bug:1189945

Change-Id: I7e88fb44c98f9e61e147decbf66569e2a9f43a09
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2866030
Reviewed-by: Tim Sergeant <tsergeant@chromium.org>
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Commit-Queue: Melissa Zhang <melzhang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#879671}
  • Loading branch information
Melissa Zhang authored and Chromium LUCI CQ committed May 6, 2021
1 parent 141c19b commit 732af78
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 43 deletions.
18 changes: 16 additions & 2 deletions chrome/browser/ui/ash/sharesheet/sharesheet_constants.h
Expand Up @@ -16,9 +16,23 @@ namespace sharesheet {
constexpr int kSpacing = 24;

constexpr size_t kTextPreviewMaximumLines = 3;
constexpr gfx::Size kImagePreviewSize(::sharesheet::kIconSize,
::sharesheet::kIconSize);
constexpr size_t kImagePreviewMaxIcons = 4;
// TODO(crbug.com/1189945) |kImagePreviewHalfIconSize| value should actually be
// 19. When refactoring HoldingSpaceImage, once the DCHECK_GT(20) is removed,
// this should be set to 19. At that point |kImagePreviewFullIconSize| can be
// be removed and set to |::sharesheet::kIconSize|.
constexpr size_t kImagePreviewHalfIconSize = 21;
constexpr size_t kImagePreviewFullIconSize = 44;
constexpr gfx::Size kImagePreviewFullSize(kImagePreviewFullIconSize,
kImagePreviewFullIconSize);
constexpr gfx::Size kImagePreviewHalfSize(kImagePreviewFullIconSize,
kImagePreviewHalfIconSize);
constexpr gfx::Size kImagePreviewQuarterSize(kImagePreviewHalfIconSize,
kImagePreviewHalfIconSize);
constexpr int kImagePreviewFileEnumerationLineHeight = 10;
constexpr int kImagePreviewBetweenChildSpacing = 2;
constexpr int kImagePreviewCornerRadius = 4;
constexpr int kImagePreviewIconCornerRadius = 2;
constexpr int kImagePreviewPlaceholderIconContentSize = 20;
constexpr SkColor kImagePreviewPlaceholderIconColor = gfx::kGoogleBlue600;
constexpr SkColor kImagePreviewPlaceholderBackgroundColor = gfx::kGoogleBlue050;
Expand Down
202 changes: 172 additions & 30 deletions chrome/browser/ui/ash/sharesheet/sharesheet_header_view.cc
Expand Up @@ -5,6 +5,7 @@
#include "chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h"

#include <algorithm>
#include <string>
#include <utility>

#include "ash/public/cpp/ash_typography.h"
Expand All @@ -22,6 +23,7 @@
#include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_constants.h"
#include "chrome/browser/ui/ash/sharesheet/sharesheet_util.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
Expand Down Expand Up @@ -49,19 +51,153 @@ const std::u16string ConcatenateFileNames(
return base::ASCIIToUTF16(all_file_names);
}

gfx::ImageSkia CreatePlaceholderIcon(const gfx::VectorIcon& icon) {
gfx::ImageSkia CreatePlaceholderIcon(const gfx::VectorIcon& icon,
const gfx::Size& size) {
gfx::ImageSkia file_type_icon = gfx::CreateVectorIcon(
icon, ash::sharesheet::kImagePreviewPlaceholderIconContentSize,
ash::sharesheet::kImagePreviewPlaceholderIconColor);
return ash::HoldingSpaceImage::SuperimposeOverEmptyImage(
file_type_icon, ash::sharesheet::kImagePreviewSize);
return ash::HoldingSpaceImage::SuperimposeOverEmptyImage(file_type_icon,
size);
}

gfx::Size GetImagePreviewSize(size_t index, int grid_icon_count) {
switch (grid_icon_count) {
case 1:
return ash::sharesheet::kImagePreviewFullSize;
case 2:
return ash::sharesheet::kImagePreviewHalfSize;
case 3:
if (index == 0) {
return ash::sharesheet::kImagePreviewHalfSize;
} else {
return ash::sharesheet::kImagePreviewQuarterSize;
}
default:
return ash::sharesheet::kImagePreviewQuarterSize;
}
}

} // namespace

namespace ash {
namespace sharesheet {

// SharesheetHeaderView::SharesheetImagePreview
// ------------------------------------------------------

class SharesheetHeaderView::SharesheetImagePreview : public views::View {
public:
explicit SharesheetImagePreview(size_t file_count) {
SetPaintToLayer();
layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF(kImagePreviewCornerRadius));
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
/* inside_border_insets */ gfx::Insets(),
/* between_child_spacing */ kImagePreviewBetweenChildSpacing,
/* collapse_margins_spacing */ false));
SetPreferredSize(kImagePreviewFullSize);
SetBackground(views::CreateSolidBackground(SK_ColorWHITE));

size_t grid_icon_count =
(file_count > 0) ? std::min(file_count, kImagePreviewMaxIcons) : 1;
size_t enumeration = (file_count > kImagePreviewMaxIcons)
? file_count - kImagePreviewMaxIcons + 1
: 0;

if (grid_icon_count == 1) {
AddImageViewTo(this, kImagePreviewFullSize);
return;
}

// If we need to have more than 1 icon, add two rows so that we can
// layout the icons in a grid.
DCHECK_GT(grid_icon_count, 1);
AddRowToImageContainerView();
AddRowToImageContainerView();

for (size_t index = 0; index < grid_icon_count; ++index) {
// If we have |enumeration|, add it as a label at the bottom right of
// SharesheetImagePreview.
if (enumeration != 0 && index == kImagePreviewMaxIcons - 1) {
// TODO(crbug.com/1189945) : Add a sharesheet context to replace
// |CONTEXT_DOWNLOAD_SHELF_STATUS|.
auto* label =
children()[1]->AddChildView(std::make_unique<views::Label>(
base::StrCat({u"+", base::NumberToString16(enumeration)}),
CONTEXT_DOWNLOAD_SHELF_STATUS, ash::STYLE_SHARESHEET));
label->SetLineHeight(kImagePreviewFileEnumerationLineHeight);
label->SetEnabledColor(kButtonTextColor);
label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
label->SetBackground(views::CreateSolidBackground(
kImagePreviewPlaceholderBackgroundColor));
label->SetPreferredSize(kImagePreviewQuarterSize);
return;
}
AddImageViewAt(index, grid_icon_count,
GetImagePreviewSize(index, grid_icon_count));
}
}

SharesheetImagePreview(const SharesheetImagePreview&) = delete;
SharesheetImagePreview& operator=(const SharesheetImagePreview&) = delete;
~SharesheetImagePreview() override = default;

views::ImageView* GetImageViewAt(size_t index) {
if (index >= image_views_.size()) {
return nullptr;
}
return image_views_[index];
}

const size_t GetImageViewCount() { return image_views_.size(); }

private:
void AddRowToImageContainerView() {
auto* row = AddChildView(std::make_unique<views::View>());
row->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
/* inside_border_insets */ gfx::Insets(),
/* between_child_spacing */ kImagePreviewBetweenChildSpacing,
/* collapse_margins_spacing */ false));
}

void AddImageViewTo(views::View* parent_view, const gfx::Size& size) {
auto* image_view =
parent_view->AddChildView(std::make_unique<views::ImageView>());
image_view->SetImageSize(size);
image_view->SetBackground(
views::CreateSolidBackground(kImagePreviewPlaceholderBackgroundColor));
image_view->SetPaintToLayer();
image_view->layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF(kImagePreviewIconCornerRadius));
image_views_.push_back(image_view);
}

void AddImageViewAt(size_t index,
size_t grid_icon_count,
const gfx::Size& size) {
views::View* parent_view = this;
if (grid_icon_count > 1) {
int row_num = 0;
// For 2 icons, add to the second row for the second icons.
// For 3 icons, add to the second row for the second and third icons.
// For 4+ icons, add to the second row for the third and fourth icons.
if ((grid_icon_count == 2 && index == 1) ||
(grid_icon_count == 3 && index != 0) ||
(grid_icon_count >= 4 && index > 1)) {
row_num = 1;
}
parent_view = children()[row_num];
}
AddImageViewTo(parent_view, size);
}

std::vector<views::ImageView*> image_views_;
};

// SharesheetHeaderView --------------------------------------------------------

SharesheetHeaderView::SharesheetHeaderView(apps::mojom::IntentPtr intent,
Profile* profile)
: profile_(profile),
Expand All @@ -78,9 +214,13 @@ SharesheetHeaderView::SharesheetHeaderView(apps::mojom::IntentPtr intent,
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);

const bool has_files =
(intent_->file_urls.has_value() && !intent_->file_urls.value().empty());
// The image view is initialised first to ensure its left most placement.
if (base::FeatureList::IsEnabled(features::kSharesheetContentPreviews)) {
InitaliseImageView();
auto file_count = (has_files) ? intent_->file_urls.value().size() : 0;
image_preview_ =
AddChildView(std::make_unique<SharesheetImagePreview>(file_count));
}
// A separate view is created for the share title and preview string views.
text_view_ = AddChildView(std::make_unique<views::View>());
Expand All @@ -94,29 +234,19 @@ SharesheetHeaderView::SharesheetHeaderView(apps::mojom::IntentPtr intent,
kTitleTextColor, gfx::ALIGN_LEFT));
if (base::FeatureList::IsEnabled(features::kSharesheetContentPreviews)) {
ShowTextPreview();

if (intent_->file_urls.has_value() && !intent_->file_urls.value().empty()) {
ResolveImage();
if (has_files) {
ResolveImages();
} else {
// TODO(crbug.com/2650014): Update to text icon.
image_preview_->SetImage(
CreatePlaceholderIcon(chromeos::kFiletypeGenericIcon));
DCHECK_GT(image_preview_->GetImageViewCount(), 0);
image_preview_->GetImageViewAt(0)->SetImage(CreatePlaceholderIcon(
chromeos::kFiletypeGenericIcon, kImagePreviewFullSize));
}
}
}

SharesheetHeaderView::~SharesheetHeaderView() = default;

void SharesheetHeaderView::InitaliseImageView() {
image_preview_ = AddChildView(std::make_unique<views::ImageView>());
image_preview_->SetImageSize(kImagePreviewSize);
image_preview_->SetPaintToLayer();
image_preview_->layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF(kImagePreviewCornerRadius));
image_preview_->SetBackground(
views::CreateSolidBackground(kImagePreviewPlaceholderBackgroundColor));
}

void SharesheetHeaderView::ShowTextPreview() {
std::vector<std::u16string> text_fields = ExtractShareText();

Expand Down Expand Up @@ -224,25 +354,35 @@ std::vector<std::u16string> SharesheetHeaderView::ExtractShareText() {
return text_fields;
}

// TODO(crbug.com/2650014) Optimise to load several images.
void SharesheetHeaderView::ResolveImage() {
void SharesheetHeaderView::ResolveImages() {
for (int i = 0; i < image_preview_->GetImageViewCount(); ++i) {
ResolveImage(i);
}
}

void SharesheetHeaderView::ResolveImage(size_t index) {
base::FilePath file_path;
storage::FileSystemContext* fs_context =
file_manager::util::GetFileSystemContextForExtensionId(
profile_, file_manager::kFileManagerAppId);
storage::FileSystemURL fs_url =
fs_context->CrackURL(intent_->file_urls.value().front());
fs_context->CrackURL(intent_->file_urls.value()[index]);
file_path = fs_url.path();

image_ = std::make_unique<HoldingSpaceImage>(
kImagePreviewSize, file_path,
const auto size =
GetImagePreviewSize(index, intent_->file_urls.value().size());
auto image = std::make_unique<HoldingSpaceImage>(
size, file_path,
base::BindRepeating(&SharesheetHeaderView::LoadImage,
weak_ptr_factory_.GetWeakPtr()),
base::Optional<gfx::ImageSkia>(
CreatePlaceholderIcon(chromeos::kFiletypeImageIcon)));
image_subscription_ = image_->AddImageSkiaChangedCallback(base::BindRepeating(
&SharesheetHeaderView::OnImageLoaded, weak_ptr_factory_.GetWeakPtr()));
image_preview_->SetImage(image_->GetImageSkia(kImagePreviewSize));
CreatePlaceholderIcon(chromeos::kFiletypeImageIcon, size)));
DCHECK_GT(image_preview_->GetImageViewCount(), index);
image_preview_->GetImageViewAt(index)->SetImage(image->GetImageSkia(size));
image_subscription_.push_back(image->AddImageSkiaChangedCallback(
base::BindRepeating(&SharesheetHeaderView::OnImageLoaded,
weak_ptr_factory_.GetWeakPtr(), size, index)));
images_.push_back(std::move(image));
}

void SharesheetHeaderView::LoadImage(
Expand All @@ -256,8 +396,10 @@ void SharesheetHeaderView::LoadImage(
thumbnail_loader_.Load({file_path, size}, std::move(callback));
}

void SharesheetHeaderView::OnImageLoaded() {
image_preview_->SetImage(image_->GetImageSkia(kImagePreviewSize));
void SharesheetHeaderView::OnImageLoaded(const gfx::Size& size, size_t index) {
DCHECK_GT(image_preview_->GetImageViewCount(), index);
image_preview_->GetImageViewAt(index)->SetImage(
images_[index]->GetImageSkia(size));
}

BEGIN_METADATA(SharesheetHeaderView, views::View)
Expand Down
19 changes: 8 additions & 11 deletions chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h
Expand Up @@ -6,6 +6,7 @@
#define CHROME_BROWSER_UI_ASH_SHARESHEET_SHARESHEET_HEADER_VIEW_H_

#include <memory>
#include <vector>

#include "ash/public/cpp/holding_space/holding_space_image.h"
#include "base/callback_list.h"
Expand All @@ -16,10 +17,6 @@

class Profile;

namespace views {
class ImageView;
} // namespace views

namespace ash {
namespace sharesheet {

Expand All @@ -36,8 +33,7 @@ class SharesheetHeaderView : public views::View {
SharesheetHeaderView& operator=(const SharesheetHeaderView&) = delete;

private:
// Adds the view for image previews and sets the required properties.
void InitaliseImageView();
class SharesheetImagePreview;

// Adds the view for text preview.
void ShowTextPreview();
Expand All @@ -53,23 +49,24 @@ class SharesheetHeaderView : public views::View {
// from share_target_utils.h to a common place and reuse the function here.
std::vector<std::u16string> ExtractShareText();

void ResolveImage();
void ResolveImages();
void ResolveImage(size_t index);
void LoadImage(const base::FilePath& file_path,
const gfx::Size& size,
HoldingSpaceImage::BitmapCallback callback);
void OnImageLoaded();
void OnImageLoaded(const gfx::Size& size, size_t index);

// Contains the share title and text preview views.
views::View* text_view_ = nullptr;
views::ImageView* image_preview_ = nullptr;
SharesheetImagePreview* image_preview_;

Profile* profile_;
apps::mojom::IntentPtr intent_;

ThumbnailLoader thumbnail_loader_;
base::CallbackListSubscription image_subscription_;
std::vector<base::CallbackListSubscription> image_subscription_;
// TODO(crbug.com/1156343): Clean up to use our own FileThumbnailImage class.
std::unique_ptr<HoldingSpaceImage> image_;
std::vector<std::unique_ptr<HoldingSpaceImage>> images_;

base::WeakPtrFactory<SharesheetHeaderView> weak_ptr_factory_{this};
};
Expand Down

0 comments on commit 732af78

Please sign in to comment.