Added ability to send webp as compressed image.

This commit is contained in:
23rd 2022-11-10 18:34:37 +03:00 committed by John Preston
parent 57c50c8655
commit 3467fe226f
12 changed files with 161 additions and 45 deletions

View file

@ -3374,7 +3374,10 @@ void ApiWrap::sendFiles(
std::shared_ptr<SendingAlbum> album, std::shared_ptr<SendingAlbum> album,
const SendAction &action) { const SendAction &action) {
const auto haveCaption = !caption.text.isEmpty(); const auto haveCaption = !caption.text.isEmpty();
if (haveCaption && !list.canAddCaption(album != nullptr)) { if (haveCaption
&& !list.canAddCaption(
album != nullptr,
type == SendMediaType::Photo)) {
auto message = MessageToSend(action); auto message = MessageToSend(action);
message.textWithTags = base::take(caption); message.textWithTags = base::take(caption);
message.action.clearDraft = false; message.action.clearDraft = false;

View file

@ -102,7 +102,9 @@ void FileDialogCallback(
rpl::producer<QString> FieldPlaceholder( rpl::producer<QString> FieldPlaceholder(
const Ui::PreparedList &list, const Ui::PreparedList &list,
SendFilesWay way) { SendFilesWay way) {
return list.canAddCaption(way.groupFiles() && way.sendImagesAsPhotos()) return list.canAddCaption(
way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos())
? tr::lng_photo_caption() ? tr::lng_photo_caption()
: tr::lng_photos_comment(); : tr::lng_photos_comment();
} }
@ -390,6 +392,11 @@ void SendFilesBox::refreshAllAfterChanges(int fromItem) {
break; break;
} }
} }
{
auto sendWay = _sendWay.current();
sendWay.setHasCompressedStickers(_list.hasSticker());
_sendWay = sendWay;
}
generatePreviewFrom(fromBlock); generatePreviewFrom(fromBlock);
_inner->resizeToWidth(st::boxWideWidth); _inner->resizeToWidth(st::boxWideWidth);
refreshControls(); refreshControls();
@ -427,6 +434,7 @@ void SendFilesBox::openDialogToAddFileToAlbum() {
void SendFilesBox::initSendWay() { void SendFilesBox::initSendWay() {
_sendWay = [&] { _sendWay = [&] {
auto result = Core::App().settings().sendFilesWay(); auto result = Core::App().settings().sendFilesWay();
result.setHasCompressedStickers(_list.hasSticker());
if (_sendLimit == SendLimit::One) { if (_sendLimit == SendLimit::One) {
result.setGroupFiles(true); result.setGroupFiles(true);
return result; return result;
@ -455,7 +463,9 @@ void SendFilesBox::updateCaptionPlaceholder() {
return; return;
} }
const auto way = _sendWay.current(); const auto way = _sendWay.current();
if (!_list.canAddCaption(way.groupFiles() && way.sendImagesAsPhotos()) if (!_list.canAddCaption(
way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos())
&& _sendLimit == SendLimit::One) { && _sendLimit == SendLimit::One) {
_caption->hide(); _caption->hide();
if (_emojiToggle) { if (_emojiToggle) {
@ -668,7 +678,7 @@ void SendFilesBox::updateSendWayControlsVisibility() {
_hintLabel->setVisible( _hintLabel->setVisible(
_controller->session().settings().photoEditorHintShown() _controller->session().settings().photoEditorHintShown()
? _list.hasSendImagesAsPhotosOption(false) ? _list.canHaveEditorHintLabel()
: false); : false);
} }
@ -1019,7 +1029,8 @@ bool SendFilesBox::validateLength(const QString &text) const {
const auto way = _sendWay.current(); const auto way = _sendWay.current();
if (remove <= 0 if (remove <= 0
|| !_list.canAddCaption( || !_list.canAddCaption(
way.groupFiles() && way.sendImagesAsPhotos())) { way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos())) {
return true; return true;
} }
_controller->show(Box(CaptionLimitReachedBox, session, remove)); _controller->show(Box(CaptionLimitReachedBox, session, remove));

View file

@ -915,7 +915,8 @@ void FileLoadTask::process(Args &&args) {
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h))); attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h)));
if (ValidateThumbDimensions(w, h)) { if (ValidateThumbDimensions(w, h)) {
isSticker = Core::IsMimeSticker(filemime) isSticker = (_type == SendMediaType::File)
&& Core::IsMimeSticker(filemime)
&& (filesize < Storage::kMaxStickerBytesSize) && (filesize < Storage::kMaxStickerBytesSize)
&& (Core::IsMimeStickerAnimated(filemime) && (Core::IsMimeStickerAnimated(filemime)
|| GoodStickerDimensions(w, h)); || GoodStickerDimensions(w, h));
@ -936,6 +937,9 @@ void FileLoadTask::process(Args &&args) {
attributes.push_back(MTP_documentAttributeAnimated()); attributes.push_back(MTP_documentAttributeAnimated());
} else if (filemime.startsWith(u"image/"_q) } else if (filemime.startsWith(u"image/"_q)
&& _type != SendMediaType::File) { && _type != SendMediaType::File) {
if (Core::IsMimeSticker(filemime)) {
fullimage = Images::Opaque(std::move(fullimage));
}
auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage; auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
const auto downscaled = (w > 1280 || h > 1280); const auto downscaled = (w > 1280 || h > 1280);

View file

@ -145,7 +145,8 @@ MimeDataState ComputeMimeDataState(const QMimeData *data) {
return MimeDataState::None; return MimeDataState::None;
} }
const auto imageExtensions = Ui::ImageExtensions(); auto imageExtensions = Ui::ImageExtensions();
imageExtensions.push_back(u".webp"_q);
auto files = QStringList(); auto files = QStringList();
auto allAreSmallImages = true; auto allAreSmallImages = true;
for (const auto &url : urls) { for (const auto &url : urls) {
@ -303,11 +304,11 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
if (const auto image = std::get_if<Image>( if (const auto image = std::get_if<Image>(
&file.information->media)) { &file.information->media)) {
Assert(!image->data.isNull()); Assert(!image->data.isNull());
if (ValidPhotoForAlbum(*image, file.information->filemime)) { if (ValidPhotoForAlbum(*image, file.information->filemime)
|| Core::IsMimeSticker(file.information->filemime)) {
UpdateImageDetails(file, previewWidth); UpdateImageDetails(file, previewWidth);
file.type = PreparedFile::Type::Photo; file.type = PreparedFile::Type::Photo;
} else if (Core::IsMimeSticker(file.information->filemime) } else if (image->animated) {
|| image->animated) {
file.type = PreparedFile::Type::None; file.type = PreparedFile::Type::None;
} }
} else if (const auto video = std::get_if<Video>( } else if (const auto video = std::get_if<Video>(

View file

@ -52,8 +52,12 @@ void AlbumPreview::updateFileRows() {
Expects(_order.size() == _thumbs.size()); Expects(_order.size() == _thumbs.size());
const auto isFile = !_sendWay.sendImagesAsPhotos(); const auto isFile = !_sendWay.sendImagesAsPhotos();
auto top = 0;
for (auto i = 0; i < _order.size(); i++) { for (auto i = 0; i < _order.size(); i++) {
_thumbs[i]->updateFileRow(isFile ? _order[i] : -1); const auto &thumb = _thumbs[_order[i]];
thumb->setButtonVisible(isFile && !thumb->isCompressedSticker());
thumb->moveButtons(top);
top += thumb->fileHeight() + st::sendMediaRowSkip;
} }
} }
@ -110,6 +114,9 @@ void AlbumPreview::prepareThumbs(gsl::span<Ui::PreparedFile> items) {
this, this,
[=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); }, [=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); },
[=] { deleteThumbByIndex(thumbIndex(thumbUnderCursor())); })); [=] { deleteThumbByIndex(thumbIndex(thumbUnderCursor())); }));
if (_thumbs.back()->isCompressedSticker()) {
_hasMixedFileHeights = true;
}
} }
_thumbsHeight = countLayoutHeight(layout); _thumbsHeight = countLayoutHeight(layout);
_photosHeight = ranges::accumulate(ranges::views::all( _photosHeight = ranges::accumulate(ranges::views::all(
@ -118,9 +125,16 @@ void AlbumPreview::prepareThumbs(gsl::span<Ui::PreparedFile> items) {
return thumb->photoHeight(); return thumb->photoHeight();
}), 0) + (count - 1) * st::sendMediaRowSkip; }), 0) + (count - 1) * st::sendMediaRowSkip;
const auto &st = st::attachPreviewThumbLayout; if (!_hasMixedFileHeights) {
_filesHeight = count * st.thumbSize _filesHeight = count * _thumbs.front()->fileHeight()
+ (count - 1) * st::sendMediaRowSkip; + (count - 1) * st::sendMediaRowSkip;
} else {
_filesHeight = ranges::accumulate(ranges::views::all(
_thumbs
) | ranges::views::transform([](const auto &thumb) {
return thumb->fileHeight();
}), 0) + (count - 1) * st::sendMediaRowSkip;
}
} }
int AlbumPreview::contentLeft() const { int AlbumPreview::contentLeft() const {
@ -143,7 +157,7 @@ AlbumThumbnail *AlbumPreview::findThumb(QPoint position) const {
} else { } else {
const auto bottom = top + (isPhotosWay const auto bottom = top + (isPhotosWay
? thumb->photoHeight() ? thumb->photoHeight()
: st::attachPreviewThumbLayout.thumbSize); : thumb->fileHeight());
const auto isUnderTop = (position.y() > top); const auto isUnderTop = (position.y() > top);
top = bottom + skip; top = bottom + skip;
return isUnderTop && (position.y() < bottom); return isUnderTop && (position.y() < bottom);
@ -319,18 +333,44 @@ void AlbumPreview::paintPhotos(Painter &p, QRect clip) const {
} }
void AlbumPreview::paintFiles(Painter &p, QRect clip) const { void AlbumPreview::paintFiles(Painter &p, QRect clip) const {
const auto fileHeight = st::attachPreviewThumbLayout.thumbSize
+ st::sendMediaRowSkip;
const auto bottom = clip.y() + clip.height();
const auto from = std::clamp(clip.y() / fileHeight, 0, int(_thumbs.size()));
const auto till = std::clamp((bottom + fileHeight - 1) / fileHeight, 0, int(_thumbs.size()));
const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2; const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2;
const auto outerWidth = width(); const auto outerWidth = width();
if (!_hasMixedFileHeights) {
const auto fileHeight = st::attachPreviewThumbLayout.thumbSize
+ st::sendMediaRowSkip;
const auto bottom = clip.y() + clip.height();
const auto from = std::clamp(
clip.y() / fileHeight,
0,
int(_thumbs.size()));
const auto till = std::clamp(
(bottom + fileHeight - 1) / fileHeight,
0,
int(_thumbs.size()));
auto top = from * fileHeight; auto top = from * fileHeight;
for (auto i = from; i != till; ++i) { for (auto i = from; i != till; ++i) {
_thumbs[i]->paintFile(p, left, top, outerWidth); _thumbs[i]->paintFile(p, left, top, outerWidth);
top += fileHeight; top += fileHeight;
}
} else {
auto top = 0;
for (const auto &thumb : _thumbs) {
const auto bottom = top + thumb->fileHeight();
const auto guard = gsl::finally([&] {
top = bottom + st::sendMediaRowSkip;
});
if (top >= clip.y() + clip.height()) {
break;
} else if (bottom <= clip.y()) {
continue;
}
if (thumb->isCompressedSticker()) {
thumb->paintPhoto(p, left, top, outerWidth);
} else {
thumb->paintFile(p, left, top, outerWidth);
}
}
} }
} }
@ -421,7 +461,7 @@ void AlbumPreview::mousePressEvent(QMouseEvent *e) {
} }
void AlbumPreview::mouseMoveEvent(QMouseEvent *e) { void AlbumPreview::mouseMoveEvent(QMouseEvent *e) {
if (!_sendWay.sendImagesAsPhotos()) { if (!_sendWay.sendImagesAsPhotos() && !_hasMixedFileHeights) {
applyCursor(style::cur_default); applyCursor(style::cur_default);
return; return;
} }

View file

@ -88,6 +88,8 @@ private:
int _photosHeight = 0; int _photosHeight = 0;
int _filesHeight = 0; int _filesHeight = 0;
bool _hasMixedFileHeights = false;
AlbumThumbnail *_draggedThumb = nullptr; AlbumThumbnail *_draggedThumb = nullptr;
AlbumThumbnail *_suggestedThumb = nullptr; AlbumThumbnail *_suggestedThumb = nullptr;
AlbumThumbnail *_paintedAbove = nullptr; AlbumThumbnail *_paintedAbove = nullptr;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "ui/chat/attach/attach_album_thumbnail.h" #include "ui/chat/attach/attach_album_thumbnail.h"
#include "core/mime_type.h" // Core::IsMimeSticker.
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
#include "ui/image/image_prepare.h" #include "ui/image/image_prepare.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
@ -31,7 +32,8 @@ AlbumThumbnail::AlbumThumbnail(
, _fullPreview(file.preview) , _fullPreview(file.preview)
, _shrinkSize(int(std::ceil(st::roundRadiusLarge / 1.4))) , _shrinkSize(int(std::ceil(st::roundRadiusLarge / 1.4)))
, _isPhoto(file.type == PreparedFile::Type::Photo) , _isPhoto(file.type == PreparedFile::Type::Photo)
, _isVideo(file.type == PreparedFile::Type::Video) { , _isVideo(file.type == PreparedFile::Type::Video)
, _isCompressedSticker(Core::IsMimeSticker(file.information->filemime)) {
Expects(!_fullPreview.isNull()); Expects(!_fullPreview.isNull());
moveToLayout(layout); moveToLayout(layout);
@ -105,21 +107,16 @@ AlbumThumbnail::AlbumThumbnail(
_editMedia->setIconOverride(&st::sendBoxAlbumGroupEditButtonIconFile); _editMedia->setIconOverride(&st::sendBoxAlbumGroupEditButtonIconFile);
_deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile); _deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile);
updateFileRow(-1); setButtonVisible(false);
} }
void AlbumThumbnail::updateFileRow(int row) { void AlbumThumbnail::setButtonVisible(bool value) {
if (row < 0) { _editMedia->setVisible(value);
_editMedia->hide(); _deleteMedia->setVisible(value);
_deleteMedia->hide(); }
return;
}
_editMedia->show();
_deleteMedia->show();
const auto fileHeight = st::attachPreviewThumbLayout.thumbSize void AlbumThumbnail::moveButtons(int thumbTop) {
+ st::sendMediaRowSkip; const auto top = thumbTop + st::sendBoxFileGroupSkipTop;
const auto top = row * fileHeight + st::sendBoxFileGroupSkipTop;
auto right = st::sendBoxFileGroupSkipRight + st::boxPhotoPadding.right(); auto right = st::sendBoxFileGroupSkipRight + st::boxPhotoPadding.right();
_deleteMedia->moveToRight(right, top); _deleteMedia->moveToRight(right, top);
@ -173,6 +170,16 @@ int AlbumThumbnail::photoHeight() const {
return _photo.height() / style::DevicePixelRatio(); return _photo.height() / style::DevicePixelRatio();
} }
int AlbumThumbnail::fileHeight() const {
return _isCompressedSticker
? photoHeight()
: st::attachPreviewThumbLayout.thumbSize;
}
bool AlbumThumbnail::isCompressedSticker() const {
return _isCompressedSticker;
}
void AlbumThumbnail::paintInAlbum( void AlbumThumbnail::paintInAlbum(
QPainter &p, QPainter &p,
int left, int left,
@ -422,7 +429,7 @@ bool AlbumThumbnail::containsPoint(QPoint position) const {
} }
bool AlbumThumbnail::buttonsContainPoint(QPoint position) const { bool AlbumThumbnail::buttonsContainPoint(QPoint position) const {
return (_isPhoto return ((_isPhoto && !_isCompressedSticker)
? _lastRectOfModify ? _lastRectOfModify
: _lastRectOfButtons).contains(position); : _lastRectOfButtons).contains(position);
} }
@ -431,7 +438,7 @@ AttachButtonType AlbumThumbnail::buttonTypeFromPoint(QPoint position) const {
if (!buttonsContainPoint(position)) { if (!buttonsContainPoint(position)) {
return AttachButtonType::None; return AttachButtonType::None;
} }
return !_lastRectOfButtons.contains(position) return (!_lastRectOfButtons.contains(position) && !_isCompressedSticker)
? AttachButtonType::Modify ? AttachButtonType::Modify
: (position.x() < _lastRectOfButtons.center().x()) : (position.x() < _lastRectOfButtons.center().x())
? AttachButtonType::Edit ? AttachButtonType::Edit

View file

@ -33,6 +33,7 @@ public:
void resetLayoutAnimation(); void resetLayoutAnimation();
int photoHeight() const; int photoHeight() const;
int fileHeight() const;
void paintInAlbum( void paintInAlbum(
QPainter &p, QPainter &p,
@ -53,7 +54,10 @@ public:
void suggestMove(float64 delta, Fn<void()> callback); void suggestMove(float64 delta, Fn<void()> callback);
void finishAnimations(); void finishAnimations();
void updateFileRow(int row); void setButtonVisible(bool value);
void moveButtons(int thumbTop);
bool isCompressedSticker() const;
static constexpr auto kShrinkDuration = crl::time(150); static constexpr auto kShrinkDuration = crl::time(150);
@ -89,6 +93,8 @@ private:
int _lastShrinkValue = 0; int _lastShrinkValue = 0;
AttachControls _buttons; AttachControls _buttons;
bool _isCompressedSticker = false;
QRect _lastRectOfModify; QRect _lastRectOfModify;
QRect _lastRectOfButtons; QRect _lastRectOfButtons;

View file

@ -138,7 +138,7 @@ bool PreparedList::canBeSentInSlowmodeWith(const PreparedList &other) const {
return !hasNonGrouping && (!hasFiles || !hasVideos); return !hasNonGrouping && (!hasFiles || !hasVideos);
} }
bool PreparedList::canAddCaption(bool sendingAlbum) const { bool PreparedList::canAddCaption(bool sendingAlbum, bool compress) const {
if (!filesToProcess.empty() if (!filesToProcess.empty()
|| files.empty() || files.empty()
|| files.size() > kMaxAlbumCount) { || files.size() > kMaxAlbumCount) {
@ -146,8 +146,8 @@ bool PreparedList::canAddCaption(bool sendingAlbum) const {
} }
if (files.size() == 1) { if (files.size() == 1) {
Assert(files.front().information != nullptr); Assert(files.front().information != nullptr);
const auto isSticker = Core::IsMimeSticker( const auto isSticker = (!compress
files.front().information->filemime) && Core::IsMimeSticker(files.front().information->filemime))
|| files.front().path.endsWith( || files.front().path.endsWith(
qstr(".tgs"), qstr(".tgs"),
Qt::CaseInsensitive); Qt::CaseInsensitive);
@ -198,6 +198,26 @@ bool PreparedList::hasSendImagesAsPhotosOption(bool slowmode) const {
: ranges::contains(files, Type::Photo, &PreparedFile::type); : ranges::contains(files, Type::Photo, &PreparedFile::type);
} }
bool PreparedList::canHaveEditorHintLabel() const {
for (const auto &file : files) {
if ((file.type == PreparedFile::Type::Photo)
&& !Core::IsMimeSticker(file.information->filemime)) {
return true;
}
}
return false;
}
bool PreparedList::hasSticker() const {
for (const auto &file : files) {
if ((file.type == PreparedFile::Type::Photo)
&& Core::IsMimeSticker(file.information->filemime)) {
return true;
}
}
return false;
}
int MaxAlbumItems() { int MaxAlbumItems() {
return kMaxAlbumCount; return kMaxAlbumCount;
} }

View file

@ -106,13 +106,15 @@ struct PreparedList {
std::vector<int> order); std::vector<int> order);
void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false); void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false);
[[nodiscard]] bool canAddCaption(bool sendingAlbum) const; [[nodiscard]] bool canAddCaption(bool sendingAlbum, bool compress) const;
[[nodiscard]] bool canBeSentInSlowmode() const; [[nodiscard]] bool canBeSentInSlowmode() const;
[[nodiscard]] bool canBeSentInSlowmodeWith( [[nodiscard]] bool canBeSentInSlowmodeWith(
const PreparedList &other) const; const PreparedList &other) const;
[[nodiscard]] bool hasGroupOption(bool slowmode) const; [[nodiscard]] bool hasGroupOption(bool slowmode) const;
[[nodiscard]] bool hasSendImagesAsPhotosOption(bool slowmode) const; [[nodiscard]] bool hasSendImagesAsPhotosOption(bool slowmode) const;
[[nodiscard]] bool canHaveEditorHintLabel() const;
[[nodiscard]] bool hasSticker() const;
Error error = Error::None; Error error = Error::None;
QString errorData; QString errorData;

View file

@ -13,6 +13,9 @@ void SendFilesWay::setSendImagesAsPhotos(bool value) {
if (value) { if (value) {
_flags |= Flag::SendImagesAsPhotos; _flags |= Flag::SendImagesAsPhotos;
} else { } else {
if (hasCompressedStickers()) {
setGroupFiles(false);
}
_flags &= ~Flag::SendImagesAsPhotos; _flags &= ~Flag::SendImagesAsPhotos;
} }
} }
@ -20,11 +23,22 @@ void SendFilesWay::setSendImagesAsPhotos(bool value) {
void SendFilesWay::setGroupFiles(bool value) { void SendFilesWay::setGroupFiles(bool value) {
if (value) { if (value) {
_flags |= Flag::GroupFiles; _flags |= Flag::GroupFiles;
if (hasCompressedStickers()) {
setSendImagesAsPhotos(true);
}
} else { } else {
_flags &= ~Flag::GroupFiles; _flags &= ~Flag::GroupFiles;
} }
} }
void SendFilesWay::setHasCompressedStickers(bool value) {
if (value) {
_flags |= Flag::HasCompressedStickers;
} else {
_flags &= ~Flag::HasCompressedStickers;
}
}
//enum class SendFilesWay { // Old way. Serialize should be compatible. //enum class SendFilesWay { // Old way. Serialize should be compatible.
// Album, // Album,
// Photos, // Photos,

View file

@ -28,6 +28,7 @@ public:
} }
void setGroupFiles(bool value); void setGroupFiles(bool value);
void setSendImagesAsPhotos(bool value); void setSendImagesAsPhotos(bool value);
void setHasCompressedStickers(bool value);
[[nodiscard]] inline bool operator<(const SendFilesWay &other) const { [[nodiscard]] inline bool operator<(const SendFilesWay &other) const {
return _flags < other._flags; return _flags < other._flags;
@ -53,9 +54,14 @@ public:
int32 value); int32 value);
private: private:
[[nodiscard]] bool hasCompressedStickers() const {
return (_flags & Flag::HasCompressedStickers) != 0;
}
enum class Flag : uchar { enum class Flag : uchar {
GroupFiles = (1 << 0), GroupFiles = (1 << 0),
SendImagesAsPhotos = (1 << 1), SendImagesAsPhotos = (1 << 1),
HasCompressedStickers = (1 << 2),
Default = GroupFiles | SendImagesAsPhotos, Default = GroupFiles | SendImagesAsPhotos,
}; };