Commit 7d572181 authored by wutao's avatar wutao Committed by Commit Bot

ambient: Implement Recent Highlights preview style

The Recent Highlights has 2x2 preview images.

Screenshot: https://screenshot.googleplex.com/56hG78STNTdvCGC

Bug: b/168068860
Test: Add new js tests.
Change-Id: I1c830667327e18f465f568eb8abacea0929bc409
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2486285
Commit-Queue: Tao Wu <wutao@chromium.org>
Reviewed-by: default avatarJimmy Gong <jimmyxgong@chromium.org>
Reviewed-by: default avatarXiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#822027}
parent e72c23e2
......@@ -28,7 +28,7 @@
#imageContainer {
background-color: rgba(var(--google-blue-50-rgb), .8);
border-radius: 4px;
border-radius: 8px;
display: block;
margin: 0 12px;
position: relative;
......@@ -49,7 +49,7 @@
}
#image {
border-radius: 4px;
border-radius: 8px;
display: block;
height: 160px;
object-fit: cover;
......@@ -66,6 +66,44 @@
width: 256px;
}
#rhImages {
border-radius: 8px;
display: block;
height: 160px;
position: absolute;
transform: scale(1.0);
transition: transform 240ms;
width: 160px;
}
.image-rh {
border-radius: 8px;
display: block;
height: 78px;
position: absolute;
width: 78px;
}
.top-left {
left: 0;
top: 0;
}
.top-right {
left: 82px;
top: 0;
}
.bottom-left {
left: 0;
top: 82px;
}
.bottom-right {
left: 82px;
top: 82px;
}
.check {
display: block;
position: absolute;
......@@ -83,9 +121,7 @@
}
:host([checked]) #image {
border-radius: 6px;
box-shadow: 0 4px 8px rgba(32, 33, 36, 0.17);
transform: scale(0.9);
transition: transform 240ms;
}
......@@ -96,13 +132,41 @@
:host([checked]) #image.art-album {
transform: scale(0.875);
}
:host([checked]) #rhImages {
transform: scale(0.8);
}
</style>
<div id="albumContainer"
class$="layout vertical left flex [[computeClass_(topicSource)]]">
<div id="imageContainer" class$="[[computeClass_(topicSource)]]"
aria-hidden="true">
<!-- Only shows the image and icon when the URL is successfully
<!-- Only shows the images and icon when the URLs are successfully
fetched -->
<template is="dom-if" if="[[album.recentHighlightsUrls]]">
<div id="rhImages" actionable on-click="onImageClick_">
<img class="image-rh top-left"
src="[[album.recentHighlightsUrls.0]]"
hidden="[[!album.recentHighlightsUrls.0]]">
</img>
<img class="image-rh top-right"
src="[[album.recentHighlightsUrls.1]]"
hidden="[[!album.recentHighlightsUrls.1]]">
</img>
<img class="image-rh bottom-left"
src="[[album.recentHighlightsUrls.2]]"
hidden="[[!album.recentHighlightsUrls.2]]">
</img>
<img class="image-rh bottom-right"
src="[[album.recentHighlightsUrls.3]]"
hidden="[[!album.recentHighlightsUrls.3]]">
</img>
</div>
<iron-icon icon="os-settings:ic-checked-filled"
class$="check [[computeClass_(topicSource)]]"
hidden="[[!album.checked]]">
</iron-icon>
</template>
<template is="dom-if" if="[[album.url]]">
<img id="image" class$="[[computeClass_(topicSource)]]" actionable
src="[[album.url]]" on-click="onImageClick_">
......
......@@ -115,7 +115,13 @@ Polymer({
for (let i = 0; i < this.albums.length; ++i) {
if (this.albums[i].albumId === album.albumId) {
this.set('albums.' + i + '.url', album.url);
if (album.url) {
this.set('albums.' + i + '.url', album.url);
continue;
}
this.set(
'albums.' + i + '.recentHighlightsUrls',
album.recentHighlightsUrls);
}
}
},
......
......@@ -40,6 +40,7 @@
* description: string,
* title: string,
* url: string,
* recentHighlightsUrls: Array<string>,
* }}
*/
/* #export */ let AmbientModeAlbum;
......
......@@ -11,6 +11,7 @@
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/public/cpp/ambient/common/ambient_settings.h"
#include "ash/public/cpp/image_downloader.h"
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
......@@ -275,7 +276,6 @@ void AmbientModeHandler::SendAlbums(ash::AmbientModeTopicSource topic_source) {
value.SetKey("checked", base::Value(album.selected));
value.SetKey("description", base::Value(GetAlbumDescription(album)));
value.SetKey("title", base::Value(album.album_name));
value.SetKey("url", base::Value(album.png_data_url));
albums.Append(std::move(value));
}
break;
......@@ -288,7 +288,6 @@ void AmbientModeHandler::SendAlbums(ash::AmbientModeTopicSource topic_source) {
value.SetKey("checked", base::Value(setting.enabled));
value.SetKey("description", base::Value(setting.description));
value.SetKey("title", base::Value(setting.title));
value.SetKey("url", base::Value(setting.png_data_url));
albums.Append(std::move(value));
}
break;
......@@ -312,6 +311,32 @@ void AmbientModeHandler::SendAlbumPreview(
FireWebUIListener("album-preview-changed", std::move(album));
}
void AmbientModeHandler::SendRecentHighlightsPreviews() {
if (!FindPersonalAlbumById(ash::kAmbientModeRecentHighlightsAlbumId))
return;
base::Value png_data_urls(base::Value::Type::LIST);
for (const auto& image : recent_highlights_preview_images_) {
if (image.isNull())
continue;
std::vector<unsigned char> encoded_image_bytes;
EncodeImage(image, &encoded_image_bytes);
if (!encoded_image_bytes.empty()) {
png_data_urls.Append(base::Value(webui::GetPngDataUrl(
&encoded_image_bytes.front(), encoded_image_bytes.size())));
}
}
base::Value album(base::Value::Type::DICTIONARY);
album.SetKey("albumId",
base::Value(ash::kAmbientModeRecentHighlightsAlbumId));
album.SetKey("topicSource", base::Value(static_cast<int>(
ash::AmbientModeTopicSource::kGooglePhotos)));
album.SetKey("recentHighlightsUrls", std::move(png_data_urls));
FireWebUIListener("album-preview-changed", std::move(album));
}
void AmbientModeHandler::UpdateSettings() {
// Prevent fetch settings callback changing |settings_| and |personal_albums_|
// while updating.
......@@ -516,6 +541,11 @@ void AmbientModeHandler::DownloadAlbumPreviewImage(
// TODO(b/163413738): Slow down the downloading when there are too many
// albums.
for (const auto& album : personal_albums_.albums) {
if (album.album_id == ash::kAmbientModeRecentHighlightsAlbumId) {
DownloadRecentHighlightsPreviewImages(album.preview_image_urls);
continue;
}
ash::ImageDownloader::Get()->Download(
GURL(album.banner_image_url), NO_TRAFFIC_ANNOTATION_YET,
base::BindOnce(&AmbientModeHandler::OnAlbumPreviewImageDownloaded,
......@@ -561,6 +591,40 @@ void AmbientModeHandler::OnAlbumPreviewImageDownloaded(
encoded_image_bytes.size()));
}
void AmbientModeHandler::DownloadRecentHighlightsPreviewImages(
const std::vector<std::string>& urls) {
recent_highlights_previews_weak_factory_.InvalidateWeakPtrs();
// Only show up to 4 previews.
constexpr int kMaxRecentHighlightsPreviews = 4;
const int total_previews =
std::min(kMaxRecentHighlightsPreviews, static_cast<int>(urls.size()));
recent_highlights_preview_images_.resize(total_previews);
auto on_done = base::BarrierClosure(
total_previews,
base::BindOnce(&AmbientModeHandler::SendRecentHighlightsPreviews,
recent_highlights_previews_weak_factory_.GetWeakPtr()));
for (int url_index = 0; url_index < total_previews; ++url_index) {
const auto& url = urls[url_index];
ash::ImageDownloader::Get()->Download(
GURL(url), NO_TRAFFIC_ANNOTATION_YET,
base::BindOnce(
[](std::vector<gfx::ImageSkia>* preview_images, int url_index,
base::RepeatingClosure on_done,
base::WeakPtr<AmbientModeHandler> weak_ptr,
const gfx::ImageSkia& image) {
if (!weak_ptr)
return;
(*preview_images)[url_index] = image;
on_done.Run();
},
&recent_highlights_preview_images_, url_index, on_done,
recent_highlights_previews_weak_factory_.GetWeakPtr()));
}
}
ash::PersonalAlbum* AmbientModeHandler::FindPersonalAlbumById(
const std::string& album_id) {
auto it = std::find_if(
......
......@@ -75,6 +75,10 @@ class AmbientModeHandler : public ::settings::SettingsPageUIHandler {
const std::string& album_id,
std::string&& png_data_url);
// Send the "album-preview-changed" WebUIListener event with Recent Highlights
// previews in the |topic_source|.
void SendRecentHighlightsPreviews();
// Update the local |settings_| to server.
void UpdateSettings();
......@@ -112,6 +116,8 @@ class AmbientModeHandler : public ::settings::SettingsPageUIHandler {
void MaybeUpdateTopicSource(ash::AmbientModeTopicSource topic_source);
void DownloadAlbumPreviewImage(ash::AmbientModeTopicSource topic_source);
void DownloadRecentHighlightsPreviewImages(
const std::vector<std::string>& urls);
void OnAlbumPreviewImageDownloaded(ash::AmbientModeTopicSource topic_source,
const std::string& album_id,
......@@ -154,8 +160,12 @@ class AmbientModeHandler : public ::settings::SettingsPageUIHandler {
// Backoff retries for UpdateSettings().
net::BackoffEntry update_settings_retry_backoff_;
std::vector<gfx::ImageSkia> recent_highlights_preview_images_;
base::WeakPtrFactory<AmbientModeHandler> backend_weak_factory_{this};
base::WeakPtrFactory<AmbientModeHandler> ui_update_weak_factory_{this};
base::WeakPtrFactory<AmbientModeHandler>
recent_highlights_previews_weak_factory_{this};
};
} // namespace settings
......
......@@ -300,7 +300,7 @@ suite('AmbientModeHandler', function() {
});
test('artPhotosCheckIconHasCorrectPosition', function() {
assertCheckPosition(AmbientModeTopicSource.GOOGLE_PHOTOS);
assertCheckPosition(AmbientModeTopicSource.ART_GALLERY);
});
test('setSelectedAlbums', async () => {
......@@ -405,7 +405,7 @@ suite('AmbientModeHandler', function() {
test('updateAlbumURL', function() {
ambientModePhotosPage.albums = [
{albumId: 'id0', checked: true, title: 'album0', url: ''},
{albumId: 'id0', checked: true, title: 'album0'},
];
ambientModePhotosPage.topicSource = AmbientModeTopicSource.ART_GALLERY;
Polymer.dom.flush();
......@@ -416,10 +416,9 @@ suite('AmbientModeHandler', function() {
assertEquals(1, albumItems.length);
const album0 = albumItems[0];
assertEquals('', album0.album.url);
// Update album URL.
const url = 'chrome://ambient';
const url = 'url';
cr.webUIListenerCallback('album-preview-changed', {
topicSource: AmbientModeTopicSource.ART_GALLERY,
albumId: 'id0',
......@@ -430,7 +429,7 @@ suite('AmbientModeHandler', function() {
test('notUpdateAlbumURL', function() {
ambientModePhotosPage.albums = [
{albumId: 'id0', checked: true, title: 'album0', url: ''},
{albumId: 'id0', checked: true, title: 'album0'},
];
ambientModePhotosPage.topicSource = AmbientModeTopicSource.ART_GALLERY;
Polymer.dom.flush();
......@@ -441,7 +440,6 @@ suite('AmbientModeHandler', function() {
assertEquals(1, albumItems.length);
const album0 = albumItems[0];
assertEquals('', album0.album.url);
// Different topic source will no update album URL.
const url = 'chrome://ambient';
......@@ -450,12 +448,12 @@ suite('AmbientModeHandler', function() {
albumId: 'id0',
url: url
});
assertEquals('', album0.album.url);
assertFalse(!!album0.album.url);
});
test('updateImgVisibility', function() {
ambientModePhotosPage.albums = [
{albumId: 'id0', checked: true, title: 'album0', url: ''},
{albumId: 'id0', checked: true, title: 'album0'},
];
ambientModePhotosPage.topicSource = AmbientModeTopicSource.ART_GALLERY;
Polymer.dom.flush();
......@@ -466,13 +464,12 @@ suite('AmbientModeHandler', function() {
assertEquals(1, albumItems.length);
const album0 = albumItems[0];
assertEquals('', album0.album.url);
let img = album0.$$('#image');
assertFalse(!!img);
// Update album URL.
const url = 'https://ambient-art-gallery-preview-url';
const url = 'url';
cr.webUIListenerCallback('album-preview-changed', {
topicSource: AmbientModeTopicSource.ART_GALLERY,
albumId: 'id0',
......@@ -483,5 +480,102 @@ suite('AmbientModeHandler', function() {
img = album0.$$('#image');
assertTrue(!!img);
assertFalse(img.hidden);
const images = album0.$$('#rhImages');
assertFalse(!!images);
});
test('updateRecentHighlightsImagesVisibility', function() {
ambientModePhotosPage.albums = [
{albumId: 'id0', checked: true, title: 'album0'},
];
ambientModePhotosPage.topicSource = AmbientModeTopicSource.GOOGLE_PHOTOS;
Polymer.dom.flush();
const albumList = ambientModePhotosPage.$$('album-list');
const ironList = albumList.$$('iron-list');
const albumItems = ironList.querySelectorAll('album-item:not([hidden])');
assertEquals(1, albumItems.length);
const album0 = albumItems[0];
let images = album0.$$('#rhImages');
assertFalse(!!images);
// Update Recent Highlights album URLs.
const url = 'url';
cr.webUIListenerCallback('album-preview-changed', {
topicSource: AmbientModeTopicSource.GOOGLE_PHOTOS,
albumId: 'id0',
recentHighlightsUrls: [url, url, url, url]
});
assertEquals(url, album0.album.recentHighlightsUrls[0]);
assertEquals(url, album0.album.recentHighlightsUrls[1]);
assertEquals(url, album0.album.recentHighlightsUrls[2]);
assertEquals(url, album0.album.recentHighlightsUrls[3]);
images = album0.$$('#rhImages');
assertTrue(!!images);
assertFalse(images.hidden);
const image_top_left = album0.$$('.image-rh.top-left');
const image_top_right = album0.$$('.image-rh.top-right');
const image_bottom_left = album0.$$('.image-rh.bottom-left');
const image_bottom_right = album0.$$('.image-rh.bottom-right');
assertTrue(!!image_top_left);
assertFalse(image_top_left.hidden);
assertTrue(!!image_top_right);
assertFalse(image_top_right.hidden);
assertTrue(!!image_bottom_left);
assertFalse(image_bottom_left.hidden);
assertTrue(!!image_bottom_right);
assertFalse(image_bottom_right.hidden);
const img = album0.$$('#image');
assertFalse(!!img);
});
test('updateRecentHighlightsImagesVisibilityWithThreeImages', function() {
ambientModePhotosPage.albums = [
{albumId: 'id0', checked: true, title: 'album0'},
];
ambientModePhotosPage.topicSource = AmbientModeTopicSource.GOOGLE_PHOTOS;
Polymer.dom.flush();
const albumList = ambientModePhotosPage.$$('album-list');
const ironList = albumList.$$('iron-list');
const albumItems = ironList.querySelectorAll('album-item:not([hidden])');
assertEquals(1, albumItems.length);
const album0 = albumItems[0];
let images = album0.$$('#rhImages');
assertFalse(!!images);
// Only update 3 images.
const url = 'url';
cr.webUIListenerCallback('album-preview-changed', {
topicSource: AmbientModeTopicSource.GOOGLE_PHOTOS,
albumId: 'id0',
recentHighlightsUrls: [url, url, url]
});
assertEquals(url, album0.album.recentHighlightsUrls[0]);
assertEquals(url, album0.album.recentHighlightsUrls[1]);
assertEquals(url, album0.album.recentHighlightsUrls[2]);
assertFalse(!!album0.album.recentHighlightsUrls[3]);
images = album0.$$('#rhImages');
assertTrue(!!images);
assertFalse(images.hidden);
const image_top_left = album0.$$('.image-rh.top-left');
const image_top_right = album0.$$('.image-rh.top-right');
const image_bottom_left = album0.$$('.image-rh.bottom-left');
const image_bottom_right = album0.$$('.image-rh.bottom-right');
assertTrue(!!image_top_left);
assertFalse(image_top_left.hidden);
assertTrue(!!image_top_right);
assertFalse(image_top_right.hidden);
assertTrue(!!image_bottom_left);
assertFalse(image_bottom_left.hidden);
assertTrue(!!image_bottom_right);
assertTrue(image_bottom_right.hidden);
const img = album0.$$('#image');
assertFalse(!!img);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment