Show nice thumbnails for paid albums.

This commit is contained in:
John Preston 2024-06-24 14:48:47 +04:00
parent dfc422b505
commit 90dfae52f5
7 changed files with 187 additions and 88 deletions

View file

@ -34,7 +34,7 @@ constexpr auto kTransactionsLimit = 100;
const auto photo = tl.data().vphoto() const auto photo = tl.data().vphoto()
? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation()) ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
: nullptr; : nullptr;
auto extended = std::vector<CreditsHistoryEntry::Media>(); auto extended = std::vector<CreditsHistoryMedia>();
if (const auto list = tl.data().vextended_media()) { if (const auto list = tl.data().vextended_media()) {
extended.reserve(list->v.size()); extended.reserve(list->v.size());
for (const auto &media : list->v) { for (const auto &media : list->v) {
@ -42,9 +42,9 @@ constexpr auto kTransactionsLimit = 100;
if (const auto inner = photo.vphoto()) { if (const auto inner = photo.vphoto()) {
const auto photo = owner->processPhoto(*inner); const auto photo = owner->processPhoto(*inner);
if (!photo->isNull()) { if (!photo->isNull()) {
extended.push_back(CreditsHistoryEntry::Media{ extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryEntry::MediaType::Photo, .type = CreditsHistoryMediaType::Photo,
.mediaId = photo->id, .id = photo->id,
}); });
} }
} }
@ -54,9 +54,9 @@ constexpr auto kTransactionsLimit = 100;
if (document->isAnimation() if (document->isAnimation()
|| document->isVideoFile() || document->isVideoFile()
|| document->isGifv()) { || document->isGifv()) {
extended.push_back(CreditsHistoryEntry::Media{ extended.push_back(CreditsHistoryMedia{
.type = CreditsHistoryEntry::MediaType::Video, .type = CreditsHistoryMediaType::Video,
.mediaId = document->id, .id = document->id,
}); });
} }
} }

View file

@ -156,11 +156,18 @@ struct PaidMediaData {
not_null<Payments::CreditsFormData*> form, not_null<Payments::CreditsFormData*> form,
int photoSize) { int photoSize) {
if (const auto data = LookupPaidMediaData(session, form)) { if (const auto data = LookupPaidMediaData(session, form)) {
const auto first = data.invoice->extendedMedia.front().get(); const auto first = data.invoice->extendedMedia[0]->photo();
if (const auto photo = first->photo()) { const auto second = (data.photos > 1)
if (photo->extendedMediaPreview()) { ? data.invoice->extendedMedia[1]->photo()
return Settings::PaidMediaPhoto(parent, photo, photoSize); : nullptr;
} const auto totalCount = int(data.invoice->extendedMedia.size());
if (first && first->extendedMediaPreview()) {
return Settings::PaidMediaThumbnail(
parent,
first,
second,
totalCount,
photoSize);
} }
} }
if (form->photo) { if (form->photo) {

View file

@ -19,6 +19,16 @@ struct CreditTopupOption final {
using CreditTopupOptions = std::vector<CreditTopupOption>; using CreditTopupOptions = std::vector<CreditTopupOption>;
enum class CreditsHistoryMediaType {
Photo,
Video,
};
struct CreditsHistoryMedia {
CreditsHistoryMediaType type = CreditsHistoryMediaType::Photo;
uint64 id = 0;
};
struct CreditsHistoryEntry final { struct CreditsHistoryEntry final {
using PhotoId = uint64; using PhotoId = uint64;
enum class PeerType { enum class PeerType {
@ -30,21 +40,13 @@ struct CreditsHistoryEntry final {
PremiumBot, PremiumBot,
Ads, Ads,
}; };
enum class MediaType {
Photo,
Video,
};
struct Media {
MediaType type = MediaType::Photo;
uint64 mediaId = 0;
};
QString id; QString id;
QString title; QString title;
QString description; QString description;
QDateTime date; QDateTime date;
PhotoId photoId = 0; PhotoId photoId = 0;
std::vector<Media> extended; std::vector<CreditsHistoryMedia> extended;
uint64 credits = 0; uint64 credits = 0;
uint64 bareMsgId = 0; uint64 bareMsgId = 0;
uint64 barePeerId = 0; uint64 barePeerId = 0;

View file

@ -569,26 +569,20 @@ object_ptr<Ui::RpWidget> HistoryEntryPhoto(
photoSize); photoSize);
} }
object_ptr<Ui::RpWidget> HistoryEntryVideo( object_ptr<Ui::RpWidget> PaidMediaThumbnail(
not_null<Ui::RpWidget*> parent,
not_null<DocumentData*> video,
int photoSize) {
return GenericEntryPhoto(
parent,
[=](Fn<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(video, update);
},
photoSize);
}
object_ptr<Ui::RpWidget> PaidMediaPhoto(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
PhotoData *second,
int totalCount,
int photoSize) { int photoSize) {
return GenericEntryPhoto( return GenericEntryPhoto(
parent, parent,
[=](Fn<void()> update) { [=](Fn<void()> update) {
return Ui::GeneratePaidMediaPaintCallback(photo, update); return Ui::GeneratePaidMediaPaintCallback(
photo,
second,
totalCount,
update);
}, },
photoSize); photoSize);
} }

View file

@ -65,14 +65,11 @@ void ReceiptCreditsBox(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
int photoSize); int photoSize);
[[nodiscard]] object_ptr<Ui::RpWidget> HistoryEntryVideo( [[nodiscard]] object_ptr<Ui::RpWidget> PaidMediaThumbnail(
not_null<Ui::RpWidget*> parent,
not_null<DocumentData*> video,
int photoSize);
[[nodiscard]] object_ptr<Ui::RpWidget> PaidMediaPhoto(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
PhotoData *second,
int totalCount,
int photoSize); int photoSize);
void SmallBalanceBox( void SmallBalanceBox(

View file

@ -37,21 +37,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtSvg/QSvgRenderer> #include <QtSvg/QSvgRenderer>
namespace Ui { namespace Ui {
namespace {
using PaintRoundImageCallback = Fn<void( PaintRoundImageCallback MultiThumbnail(
Painter &p, PaintRoundImageCallback first,
int x, PaintRoundImageCallback second,
int y, int totalCount) {
int outerWidth, const auto cache = std::make_shared<QImage>();
int size)>; return [=](Painter &p, int x, int y, int outerWidth, int size) {
const auto stroke = st::lineWidth * 2;
const auto shift = stroke * 3;
if (size <= 2 * shift) {
first(p, x, y, outerWidth, size);
return;
}
const auto smaller = size - shift;
const auto ratio = style::DevicePixelRatio();
const auto full = QSize(size, size) * ratio;
if (cache->size() != full) {
*cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
cache->setDevicePixelRatio(ratio);
}
cache->fill(Qt::transparent);
auto q = Painter(cache.get());
second(q, shift, 0, outerWidth, smaller);
q.setCompositionMode(QPainter::CompositionMode_Source);
q.setPen(QPen(Qt::transparent, 2 * stroke));
q.setBrush(Qt::NoBrush);
const auto radius = st::roundRadiusLarge;
auto hq = PainterHighQualityEnabler(q);
q.drawRoundedRect(QRect(0, shift, smaller, smaller), radius, radius);
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
first(q, 0, shift, outerWidth, smaller);
q.setPen(Qt::NoPen);
q.setBrush(st::shadowFg);
q.drawRoundedRect(QRect(0, shift, smaller, smaller), radius, radius);
q.setPen(st::toastFg);
q.setFont(style::font(smaller / 2, style::FontFlag::Semibold, 0));
q.drawText(
QRect(0, shift, smaller, smaller),
QString::number(totalCount),
style::al_center);
q.end();
p.drawImage(x, y, *cache);
};
}
} // namespace
QImage GenerateStars(int height, int count) { QImage GenerateStars(int height, int count) {
constexpr auto kOutlineWidth = .6; constexpr auto kOutlineWidth = .6;
constexpr auto kStrokeWidth = 3; constexpr auto kStrokeWidth = 3;
constexpr auto kShift = 3; constexpr auto kShift = 3;
auto colorized = qs(Ui::Premium::ColorizedSvg( auto colorized = qs(Premium::ColorizedSvg(
Ui::Premium::CreditsIconGradientStops())); Premium::CreditsIconGradientStops()));
colorized.replace( colorized.replace(
u"stroke=\"none\""_q, u"stroke=\"none\""_q,
u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name()));
@ -98,10 +139,10 @@ QImage GenerateStars(int height, int count) {
return frame; return frame;
} }
not_null<Ui::RpWidget*> CreateSingleStarWidget( not_null<RpWidget*> CreateSingleStarWidget(
not_null<Ui::RpWidget*> parent, not_null<RpWidget*> parent,
int height) { int height) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent); const auto widget = CreateChild<RpWidget>(parent);
const auto image = GenerateStars(height, 1); const auto image = GenerateStars(height, 1);
widget->resize(image.size() / style::DevicePixelRatio()); widget->resize(image.size() / style::DevicePixelRatio());
widget->paintRequest( widget->paintRequest(
@ -113,15 +154,15 @@ not_null<Ui::RpWidget*> CreateSingleStarWidget(
return widget; return widget;
} }
not_null<Ui::MaskedInputField*> AddInputFieldForCredits( not_null<MaskedInputField*> AddInputFieldForCredits(
not_null<Ui::VerticalLayout*> container, not_null<VerticalLayout*> container,
rpl::producer<uint64> value) { rpl::producer<uint64> value) {
const auto &st = st::botEarnInputField; const auto &st = st::botEarnInputField;
const auto inputContainer = container->add( const auto inputContainer = container->add(
CreateSkipWidget(container, st.heightMin)); CreateSkipWidget(container, st.heightMin));
const auto currentValue = rpl::variable<uint64>( const auto currentValue = rpl::variable<uint64>(
rpl::duplicate(value)); rpl::duplicate(value));
const auto input = Ui::CreateChild<Ui::NumberInput>( const auto input = CreateChild<NumberInput>(
inputContainer, inputContainer,
st, st,
tr::lng_bot_earn_out_ph(), tr::lng_bot_earn_out_ph(),
@ -146,16 +187,16 @@ not_null<Ui::MaskedInputField*> AddInputFieldForCredits(
st::boxRowPadding.left(), st::boxRowPadding.left(),
st.textMargins.top()); st.textMargins.top());
}, input->lifetime()); }, input->lifetime());
Ui::ToggleChildrenVisibility(inputContainer, true); ToggleChildrenVisibility(inputContainer, true);
return input; return input;
} }
PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( PaintRoundImageCallback GenerateCreditsPaintUserpicCallback(
const Data::CreditsHistoryEntry &entry) { const Data::CreditsHistoryEntry &entry) {
const auto bg = [&]() -> Ui::EmptyUserpic::BgColors { const auto bg = [&]() -> EmptyUserpic::BgColors {
switch (entry.peerType) { switch (entry.peerType) {
case Data::CreditsHistoryEntry::PeerType::Peer: case Data::CreditsHistoryEntry::PeerType::Peer:
return Ui::EmptyUserpic::UserpicColor(0); return EmptyUserpic::UserpicColor(0);
case Data::CreditsHistoryEntry::PeerType::AppStore: case Data::CreditsHistoryEntry::PeerType::AppStore:
return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }; return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 };
case Data::CreditsHistoryEntry::PeerType::PlayMarket: case Data::CreditsHistoryEntry::PeerType::PlayMarket:
@ -172,7 +213,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback(
} }
Unexpected("Unknown peer type."); Unexpected("Unknown peer type.");
}(); }();
const auto userpic = std::make_shared<Ui::EmptyUserpic>(bg, QString()); const auto userpic = std::make_shared<EmptyUserpic>(bg, QString());
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
userpic->paintCircle(p, x, y, outerWidth, size); userpic->paintCircle(p, x, y, outerWidth, size);
using PeerType = Data::CreditsHistoryEntry::PeerType; using PeerType = Data::CreditsHistoryEntry::PeerType;
@ -190,7 +231,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback(
}; };
} }
Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback( PaintRoundImageCallback GenerateCreditsPaintEntryCallback(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Fn<void()> update) { Fn<void()> update) {
struct State { struct State {
@ -240,7 +281,7 @@ Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
}; };
} }
Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback( PaintRoundImageCallback GenerateCreditsPaintEntryCallback(
not_null<DocumentData*> video, not_null<DocumentData*> video,
Fn<void()> update) { Fn<void()> update) {
struct State { struct State {
@ -286,7 +327,36 @@ Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
}; };
} }
Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback( PaintRoundImageCallback GenerateCreditsPaintEntryCallback(
not_null<Main::Session*> session,
Data::CreditsHistoryMedia media,
Fn<void()> update) {
return (media.type == Data::CreditsHistoryMediaType::Photo)
? GenerateCreditsPaintEntryCallback(
session->data().photo(media.id),
std::move(update))
: GenerateCreditsPaintEntryCallback(
session->data().document(media.id),
std::move(update));
}
PaintRoundImageCallback GenerateCreditsPaintEntryCallback(
not_null<Main::Session*> session,
const std::vector<Data::CreditsHistoryMedia> &media,
Fn<void()> update) {
if (media.size() == 1) {
return GenerateCreditsPaintEntryCallback(
session,
media.front(),
std::move(update));
}
return MultiThumbnail(
GenerateCreditsPaintEntryCallback(session, media[0], update),
GenerateCreditsPaintEntryCallback(session, media[1], update),
media.size());
}
PaintRoundImageCallback GeneratePaidPhotoPaintCallback(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Fn<void()> update) { Fn<void()> update) {
struct State { struct State {
@ -294,7 +364,8 @@ Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback(
} }
QImage image; QImage image;
Ui::SpoilerAnimation spoiler; QImage spoilerCornerCache;
SpoilerAnimation spoiler;
}; };
const auto state = std::make_shared<State>(update); const auto state = std::make_shared<State>(update);
@ -322,36 +393,49 @@ Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback(
{ .options = Images::Option::RoundLarge }); { .options = Images::Option::RoundLarge });
} }
p.drawImage(x, y, state->image); p.drawImage(x, y, state->image);
Ui::FillSpoilerRect( FillSpoilerRect(
p, p,
QRect(x, y, size, size), QRect(x, y, size, size),
Ui::DefaultImageSpoiler().frame( Images::CornersMaskRef(
state->spoiler.index(crl::now(), false))); Images::CornersMask(ImageRoundRadius::Large)),
DefaultImageSpoiler().frame(
state->spoiler.index(crl::now(), false)),
state->spoilerCornerCache);
}; };
} }
Fn<Fn<void(Painter&, int, int, int, int)>(Fn<void()>)> PaintPreviewCallback( PaintRoundImageCallback GeneratePaidMediaPaintCallback(
not_null<PhotoData*> photo,
PhotoData *second,
int totalCount,
Fn<void()> update) {
if (!second) {
return GeneratePaidPhotoPaintCallback(photo, update);
}
return MultiThumbnail(
GeneratePaidPhotoPaintCallback(photo, update),
GeneratePaidPhotoPaintCallback(second, update),
totalCount);
}
Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &entry) { const Data::CreditsHistoryEntry &entry) {
using MediaType = Data::CreditsHistoryEntry::MediaType; using MediaType = Data::CreditsHistoryMediaType;
const auto &extended = entry.extended; const auto &extended = entry.extended;
const auto owner = &session->data(); if (!extended.empty()) {
const auto photo = entry.photoId
? owner->photo(entry.photoId).get()
: (!extended.empty() && extended.front().type == MediaType::Photo)
? owner->photo(extended.front().mediaId).get()
: nullptr;
const auto video = (!extended.empty()
&& extended.front().type == MediaType::Video)
? owner->document(extended.front().mediaId).get()
: nullptr;
if (photo) {
return [=](Fn<void()> update) { return [=](Fn<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(photo, update); return GenerateCreditsPaintEntryCallback(
session,
extended,
std::move(update));
}; };
} else if (video) { } else if (entry.photoId) {
const auto photo = session->data().photo(entry.photoId);
return [=](Fn<void()> update) { return [=](Fn<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(video, update); return GenerateCreditsPaintEntryCallback(
photo,
std::move(update));
}; };
} }
return nullptr; return nullptr;

View file

@ -12,6 +12,7 @@ class DocumentData;
namespace Data { namespace Data {
struct CreditsHistoryEntry; struct CreditsHistoryEntry;
struct CreditsHistoryMedia;
} // namespace Data } // namespace Data
namespace Main { namespace Main {
@ -26,6 +27,13 @@ class VerticalLayout;
namespace Ui { namespace Ui {
using PaintRoundImageCallback = Fn<void(
Painter &p,
int x,
int y,
int outerWidth,
int size)>;
[[nodiscard]] QImage GenerateStars(int height, int count); [[nodiscard]] QImage GenerateStars(int height, int count);
[[nodiscard]] not_null<Ui::RpWidget*> CreateSingleStarWidget( [[nodiscard]] not_null<Ui::RpWidget*> CreateSingleStarWidget(
@ -36,22 +44,29 @@ namespace Ui {
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
rpl::producer<uint64> value); rpl::producer<uint64> value);
Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintUserpicCallback( PaintRoundImageCallback GenerateCreditsPaintUserpicCallback(
const Data::CreditsHistoryEntry &entry); const Data::CreditsHistoryEntry &entry);
Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback( PaintRoundImageCallback GenerateCreditsPaintEntryCallback(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Fn<void()> update); Fn<void()> update);
Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback( PaintRoundImageCallback GenerateCreditsPaintEntryCallback(
not_null<DocumentData*> video, not_null<DocumentData*> video,
Fn<void()> update); Fn<void()> update);
Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback( PaintRoundImageCallback GenerateCreditsPaintEntryCallback(
not_null<PhotoData*> photo, not_null<Main::Session*> session,
Data::CreditsHistoryMedia media,
Fn<void()> update); Fn<void()> update);
Fn<Fn<void(Painter&, int, int, int, int)>(Fn<void()>)> PaintPreviewCallback( PaintRoundImageCallback GeneratePaidMediaPaintCallback(
not_null<PhotoData*> photo,
PhotoData *second,
int totalCount,
Fn<void()> update);
Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &entry); const Data::CreditsHistoryEntry &entry);