Customize pay-by-stars box for paid media.

This commit is contained in:
John Preston 2024-06-19 20:57:13 +04:00
parent 950a946a16
commit 479b63c33a
9 changed files with 229 additions and 27 deletions

View file

@ -2327,9 +2327,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_out_title" = "Confirm Your Purchase";
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
"lng_credits_box_out_media#one" = "Do you want to unlock {media} in {chat} for **{count} Star**?";
"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
"lng_credits_box_out_photo" = "a photo";
"lng_credits_box_out_photos#one" = "{count} photo";
"lng_credits_box_out_photos#other" = "{count} photos";
"lng_credits_box_out_video" = "a video";
"lng_credits_box_out_videos#one" = "{count} video";
"lng_credits_box_out_videos#other" = "{count} videos";
"lng_credits_box_out_both" = "{photo} and {video}";
"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
"lng_credits_box_out_about" = "Review the {link} for Stars.";
"lng_credits_media_done_title" = "Media Unlocked";
"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
"lng_credits_summary_in_toast_title" = "Stars Acquired";
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_credits.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
@ -39,6 +40,136 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
namespace Ui {
namespace {
struct PaidMediaData {
const Data::Invoice *invoice = nullptr;
HistoryItem *item = nullptr;
PeerData *peer = nullptr;
int photos = 0;
int videos = 0;
explicit operator bool() const {
return invoice && item && peer && (photos || videos);
}
};
[[nodiscard]] PaidMediaData LookupPaidMediaData(
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form) {
using namespace Payments;
const auto message = std::get_if<InvoiceMessage>(&form->id.value);
const auto item = message
? session->data().message(message->peer, message->itemId)
: nullptr;
const auto media = item ? item->media() : nullptr;
const auto invoice = media ? media->invoice() : nullptr;
if (!invoice || !invoice->isPaidMedia) {
return {};
}
auto photos = 0;
auto videos = 0;
for (const auto &media : invoice->extendedMedia) {
const auto photo = media->photo();
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
++photos;
} else {
++videos;
}
}
return {
.invoice = invoice,
.item = item,
.peer = message->peer,
.photos = photos,
.videos = videos,
};
}
[[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText(
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form) {
if (const auto data = LookupPaidMediaData(session, form)) {
auto photos = 0;
auto videos = 0;
for (const auto &media : data.invoice->extendedMedia) {
const auto photo = media->photo();
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
++photos;
} else {
++videos;
}
}
auto photosBold = tr::lng_credits_box_out_photos(
lt_count,
rpl::single(photos) | tr::to_count(),
Ui::Text::Bold);
auto videosBold = tr::lng_credits_box_out_videos(
lt_count,
rpl::single(videos) | tr::to_count(),
Ui::Text::Bold);
auto media = (!videos)
? ((photos > 1)
? std::move(photosBold)
: tr::lng_credits_box_out_photo(Ui::Text::WithEntities))
: (!photos)
? ((videos > 1)
? std::move(videosBold)
: tr::lng_credits_box_out_video(Ui::Text::WithEntities))
: tr::lng_credits_box_out_both(
lt_photo,
std::move(photosBold),
lt_video,
std::move(videosBold),
Ui::Text::WithEntities);
return tr::lng_credits_box_out_media(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_media,
std::move(media),
lt_chat,
rpl::single(Ui::Text::Bold(data.peer->name())),
Ui::Text::RichLangValue);
}
const auto bot = session->data().user(form->botId);
return tr::lng_credits_box_out_sure(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_text,
rpl::single(TextWithEntities{ form->title }),
lt_bot,
rpl::single(TextWithEntities{ bot->name() }),
Ui::Text::RichLangValue);
}
[[nodiscard]] object_ptr<Ui::RpWidget> SendCreditsThumbnail(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form,
int photoSize) {
if (const auto data = LookupPaidMediaData(session, form)) {
const auto first = data.invoice->extendedMedia.front().get();
if (const auto photo = first->photo()) {
if (photo->extendedMediaPreview()) {
return Settings::PaidMediaPhoto(parent, photo, photoSize);
}
}
}
if (form->photo) {
return Settings::HistoryEntryPhoto(parent, form->photo, photoSize);
}
const auto bot = session->data().user(form->botId);
return object_ptr<Ui::UserpicButton>(
parent,
bot,
st::defaultUserpicButton);
}
} // namespace
void SendCreditsBox(
not_null<Ui::GenericBox*> box,
@ -89,22 +220,10 @@ void SendCreditsBox(
}, ministarsContainer->lifetime());
}
const auto bot = session->data().user(form->botId);
if (form->photo) {
box->addRow(object_ptr<Ui::CenterWrap<>>(
content,
Settings::HistoryEntryPhoto(content, form->photo, photoSize)));
} else {
const auto widget = box->addRow(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::UserpicButton>(
content,
bot,
st::defaultUserpicButton)));
widget->setAttribute(Qt::WA_TransparentForMouseEvents);
}
const auto thumb = box->addRow(object_ptr<Ui::CenterWrap<>>(
content,
SendCreditsThumbnail(content, session, form.get(), photoSize)));
thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
@ -118,14 +237,7 @@ void SendCreditsBox(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_credits_box_out_sure(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_text,
rpl::single(TextWithEntities{ form->title }),
lt_bot,
rpl::single(TextWithEntities{ bot->name() }),
Ui::Text::RichLangValue),
SendCreditsConfirmText(session, form.get()),
st::creditsBoxAbout)));
Ui::AddSkip(content);
Ui::AddSkip(content);
@ -177,7 +289,7 @@ void SendCreditsBox(
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
button,
rpl::single(QString()),
st::defaultFlatLabel);
st::creditsBoxButtonLabel);
std::move(
buttonText
) | rpl::start_with_next([=](const TextWithEntities &text) {

View file

@ -399,6 +399,7 @@ void Form::requestForm() {
.amount = amount,
};
const auto formData = CreditsFormData{
.id = _id,
.formId = data.vform_id().v,
.botId = data.vbot_id().v,
.title = qs(data.vtitle()),

View file

@ -178,6 +178,7 @@ struct InvoiceId {
};
struct CreditsFormData {
InvoiceId id;
uint64 formId = 0;
uint64 botId = 0;
QString title;

View file

@ -544,6 +544,27 @@ object_ptr<Ui::RpWidget> HistoryEntryPhoto(
return owned;
}
object_ptr<Ui::RpWidget> PaidMediaPhoto(
not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo,
int photoSize) {
auto owned = object_ptr<Ui::RpWidget>(parent);
const auto widget = owned.data();
widget->resize(Size(photoSize));
const auto draw = Ui::GeneratePaidMediaPaintCallback(
photo,
[=] { widget->update(); });
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
draw(p, 0, 0, photoSize, photoSize);
}, widget->lifetime());
return owned;
}
void SmallBalanceBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,

View file

@ -50,6 +50,11 @@ void ReceiptCreditsBox(
not_null<PhotoData*> photo,
int photoSize);
[[nodiscard]] object_ptr<Ui::RpWidget> PaidMediaPhoto(
not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo,
int photoSize);
void SmallBalanceBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,

View file

@ -35,11 +35,12 @@ creditsBoxAbout: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
align: align(top);
}
creditsBoxAboutTitle: FlatLabel(settingsPremiumUserTitle) {
minWidth: 256px;
}
creditsBoxAboutDivider: FlatLabel(boxDividerLabel) {
align: align(top);
}
creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
}

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/rect.h"
@ -179,6 +180,50 @@ Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
};
}
Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback(
not_null<PhotoData*> photo,
Fn<void()> update) {
struct State {
explicit State(Fn<void()> update) : spoiler(std::move(update)) {
}
QImage image;
Ui::SpoilerAnimation spoiler;
};
const auto state = std::make_shared<State>(update);
return [=](Painter &p, int x, int y, int outerWidth, int size) {
if (state->image.isNull()) {
const auto media = photo->createMediaView();
const auto thumbnail = media->thumbnailInline();
const auto ratio = style::DevicePixelRatio();
const auto scaled = QSize(size, size) * ratio;
auto image = thumbnail
? Images::Blur(thumbnail->original(), true)
: QImage(scaled, QImage::Format_ARGB32_Premultiplied);
if (!thumbnail) {
image.fill(Qt::black);
image.setDevicePixelRatio(ratio);
}
const auto minSize = std::min(image.width(), image.height());
state->image = Images::Prepare(
image.copy(
(image.width() - minSize) / 2,
(image.height() - minSize) / 2,
minSize,
minSize),
size * ratio,
{ .options = Images::Option::RoundLarge });
}
p.drawImage(x, y, state->image);
Ui::FillSpoilerRect(
p,
QRect(x, y, size, size),
Ui::DefaultImageSpoiler().frame(
state->spoiler.index(crl::now(), false)));
};
}
TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) {
return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment)
? tr::lng_bot_username_description1_link

View file

@ -24,6 +24,10 @@ Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
not_null<PhotoData*> photo,
Fn<void()> update);
Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback(
not_null<PhotoData*> photo,
Fn<void()> update);
[[nodiscard]] TextWithEntities GenerateEntryName(
const Data::CreditsHistoryEntry &entry);