From 1399d2501d681374bb986b0a01db4f34c56e5b70 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Jun 2024 18:10:04 +0400 Subject: [PATCH] Show first paid media in transactions history. --- Telegram/SourceFiles/api/api_credits.cpp | 35 ++++++++- Telegram/SourceFiles/boxes/send_files_box.cpp | 9 ++- Telegram/SourceFiles/data/data_credits.h | 10 +++ .../info_statistics_list_controllers.cpp | 20 +++-- .../settings/settings_credits_graphics.cpp | 66 +++++++++------- .../settings/settings_credits_graphics.h | 10 +++ .../ui/effects/credits_graphics.cpp | 77 ++++++++++++++++++- .../SourceFiles/ui/effects/credits_graphics.h | 13 ++++ 8 files changed, 200 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 4473b78cd..e68ff026e 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/unixtime.h" #include "data/data_channel.h" +#include "data/data_document.h" #include "data/data_peer.h" #include "data/data_photo.h" #include "data/data_session.h" @@ -28,9 +29,40 @@ constexpr auto kTransactionsLimit = 100; const MTPStarsTransaction &tl, not_null peer) { using HistoryPeerTL = MTPDstarsTransactionPeer; + using namespace Data; + const auto owner = &peer->owner(); const auto photo = tl.data().vphoto() - ? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation()) + ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation()) : nullptr; + auto extended = std::vector(); + if (const auto list = tl.data().vextended_media()) { + extended.reserve(list->v.size()); + for (const auto &media : list->v) { + media.match([&](const MTPDmessageMediaPhoto &photo) { + if (const auto inner = photo.vphoto()) { + const auto photo = owner->processPhoto(*inner); + if (!photo->isNull()) { + extended.push_back(CreditsHistoryEntry::Media{ + .type = CreditsHistoryEntry::MediaType::Photo, + .mediaId = photo->id, + }); + } + } + }, [&](const MTPDmessageMediaDocument &document) { + if (const auto inner = document.vdocument()) { + const auto document = owner->processDocument(*inner); + if (document->isAnimation() + || document->isVideoFile() + || document->isGifv()) { + extended.push_back(CreditsHistoryEntry::Media{ + .type = CreditsHistoryEntry::MediaType::Video, + .mediaId = document->id, + }); + } + } + }, [&](const auto &) {}); + } + } const auto barePeerId = tl.data().vpeer().match([]( const HistoryPeerTL &p) { return peerFromMTP(p.vpeer()); @@ -53,6 +85,7 @@ constexpr auto kTransactionsLimit = 100; .description = qs(tl.data().vdescription().value_or_empty()), .date = base::unixtime::parse(tl.data().vdate().v), .photoId = photo ? photo->id : 0, + .extended = std::move(extended), .credits = tl.data().vstars().v, .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()), .barePeerId = barePeerId, diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index d1f5cfeb6..6d67aa63d 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_media_prepare.h" #include "iv/iv_instance.h" #include "mainwidget.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "mtproto/mtproto_config.h" @@ -64,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kMaxMessageLength = 4096; -constexpr auto kMaxPrice = 1000ULL; using Ui::SendFilesWay; @@ -125,6 +125,9 @@ void EditPriceBox( 0, st::defaultSubsectionTitlePadding.right(), 0))); + const auto limit = session->appConfig().get( + u"stars_paid_post_amount_max"_q, + 10'000); const auto wrap = box->addRow(object_ptr( box, st::editTagField.heightMin)); @@ -133,7 +136,7 @@ void EditPriceBox( st::editTagField, tr::lng_paid_cost_placeholder(), price ? QString::number(price) : QString(), - kMaxPrice); + limit); const auto field = owned.data(); wrap->widthValue() | rpl::start_with_next([=](int width) { field->move(0, 0); @@ -167,7 +170,7 @@ void EditPriceBox( const auto save = [=] { const auto now = field->getLastText().toULongLong(); - if (now > kMaxPrice) { + if (now > limit) { field->showError(); return; } diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 74fe1c4eb..e8d3aec66 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -30,11 +30,21 @@ struct CreditsHistoryEntry final { PremiumBot, Ads, }; + enum class MediaType { + Photo, + Video, + }; + struct Media { + MediaType type = MediaType::Photo; + uint64 mediaId = 0; + }; + QString id; QString title; QString description; QDateTime date; PhotoId photoId = 0; + std::vector extended; uint64 credits = 0; uint64 bareMsgId = 0; uint64 barePeerId = 0; diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index eb3292987..956c7d0c3 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -756,20 +756,24 @@ private: QString _name; Ui::Text::String _rightText; + + base::has_weak_ptr _guard; }; -CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) +CreditsRow::CreditsRow( + not_null peer, + const Descriptor &descriptor) : PeerListRow(peer, UniqueRowIdFromEntry(descriptor.entry)) , _entry(descriptor.entry) , _creditIcon(descriptor.creditIcon) , _rowHeight(descriptor.rowHeight) { - const auto photo = _entry.photoId - ? peer->session().data().photo(_entry.photoId).get() - : nullptr; - if (photo) { - _paintUserpicCallback = Ui::GenerateCreditsPaintEntryCallback( - photo, - [this, update = descriptor.updateCallback] { update(this); }); + const auto callback = Ui::PaintPreviewCallback( + &peer->session(), + _entry); + if (callback) { + _paintUserpicCallback = callback(crl::guard(&_guard, [this, update = descriptor.updateCallback] { + update(this); + })); } init(); } diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index b5c890309..5b1ab8374 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" #include "core/ui_integration.h" +#include "data/data_document.h" #include "data/data_file_origin.h" #include "core/click_handler_types.h" // UrlClickHandler #include "data/data_photo_media.h" @@ -350,18 +351,16 @@ void ReceiptCreditsBox( using Type = Data::CreditsHistoryEntry::PeerType; const auto &stUser = st::boostReplaceUserpic; + const auto session = &controller->session(); const auto peer = (e.peerType == Type::PremiumBot) ? premiumBot : e.barePeerId - ? controller->session().data().peer(PeerId(e.barePeerId)).get() + ? session->data().peer(PeerId(e.barePeerId)).get() : nullptr; - const auto photo = e.photoId - ? controller->session().data().photo(e.photoId).get() - : nullptr; - if (photo) { + if (const auto callback = Ui::PaintPreviewCallback(session, e)) { content->add(object_ptr>( content, - HistoryEntryPhoto(content, photo, stUser.photoSize))); + GenericEntryPhoto(content, callback, stUser.photoSize))); } else if (peer) { content->add(object_ptr>( content, @@ -539,18 +538,16 @@ void ReceiptCreditsBox( }, button->lifetime()); } -object_ptr HistoryEntryPhoto( +object_ptr GenericEntryPhoto( not_null parent, - not_null photo, + Fn(Fn)> callback, int photoSize) { auto owned = object_ptr(parent); const auto widget = owned.data(); widget->resize(Size(photoSize)); - const auto draw = Ui::GenerateCreditsPaintEntryCallback( - photo, - [=] { widget->update(); }); - + const auto draw = callback( + crl::guard(widget, [=] { widget->update(); })); widget->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(widget); @@ -560,25 +557,40 @@ object_ptr HistoryEntryPhoto( return owned; } +object_ptr HistoryEntryPhoto( + not_null parent, + not_null photo, + int photoSize) { + return GenericEntryPhoto( + parent, + [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback(photo, update); + }, + photoSize); +} + +object_ptr HistoryEntryVideo( + not_null parent, + not_null video, + int photoSize) { + return GenericEntryPhoto( + parent, + [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback(video, update); + }, + photoSize); +} + object_ptr PaidMediaPhoto( not_null parent, not_null photo, int photoSize) { - auto owned = object_ptr(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; + return GenericEntryPhoto( + parent, + [=](Fn update) { + return Ui::GeneratePaidMediaPaintCallback(photo, update); + }, + photoSize); } void SmallBalanceBox( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 8119213b7..2bc3f7f1c 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -55,11 +55,21 @@ void ReceiptCreditsBox( PeerData *premiumBot, const Data::CreditsHistoryEntry &e); +[[nodiscard]] object_ptr GenericEntryPhoto( + not_null parent, + Fn(Fn update)> callback, + int photoSize); + [[nodiscard]] object_ptr HistoryEntryPhoto( not_null parent, not_null photo, int photoSize); +[[nodiscard]] object_ptr HistoryEntryVideo( + not_null parent, + not_null video, + int photoSize); + [[nodiscard]] object_ptr PaidMediaPhoto( not_null parent, not_null photo, diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 60af867b1..c662619b6 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_credits.h" #include "data/data_file_origin.h" +#include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_session.h" @@ -232,7 +234,53 @@ Fn GenerateCreditsPaintEntryCallback( minSize, minSize), size * style::DevicePixelRatio(), - { .options = Images::Option::RoundCircle }); + { .options = Images::Option::RoundLarge }); + } + p.drawImage(x, y, state->image); + }; +} + +Fn GenerateCreditsPaintEntryCallback( + not_null video, + Fn update) { + struct State { + std::shared_ptr view; + Image *imagePtr = nullptr; + QImage image; + rpl::lifetime downloadLifetime; + bool entryImageLoaded = false; + }; + const auto state = std::make_shared(); + state->view = video->createMediaView(); + video->loadThumbnail({}); + + rpl::single(rpl::empty_value()) | rpl::then( + video->owner().session().downloaderTaskFinished() + ) | rpl::start_with_next([=] { + using Size = Data::PhotoSize; + if (const auto thumbnail = state->view->thumbnail()) { + state->imagePtr = thumbnail; + } + update(); + if (state->imagePtr) { + state->entryImageLoaded = true; + state->downloadLifetime.destroy(); + } + }, state->downloadLifetime); + + return [=](Painter &p, int x, int y, int outerWidth, int size) { + if (state->imagePtr + && (!state->entryImageLoaded || state->image.isNull())) { + const auto image = state->imagePtr->original(); + 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 * style::DevicePixelRatio(), + { .options = Images::Option::RoundLarge }); } p.drawImage(x, y, state->image); }; @@ -282,6 +330,33 @@ Fn GeneratePaidMediaPaintCallback( }; } +Fn(Fn)> PaintPreviewCallback( + not_null session, + const Data::CreditsHistoryEntry &entry) { + using MediaType = Data::CreditsHistoryEntry::MediaType; + const auto &extended = entry.extended; + const auto owner = &session->data(); + 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 update) { + return Ui::GenerateCreditsPaintEntryCallback(photo, update); + }; + } else if (video) { + return [=](Fn update) { + return Ui::GenerateCreditsPaintEntryCallback(video, update); + }; + } + return nullptr; +} + TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) ? tr::lng_bot_username_description1_link diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index 343910adf..4585fe7c0 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -8,11 +8,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once class PhotoData; +class DocumentData; namespace Data { struct CreditsHistoryEntry; } // namespace Data +namespace Main { +class Session; +} // namespace Main + namespace Ui { class MaskedInputField; class RpWidget; @@ -38,10 +43,18 @@ Fn GenerateCreditsPaintEntryCallback( not_null photo, Fn update); +Fn GenerateCreditsPaintEntryCallback( + not_null video, + Fn update); + Fn GeneratePaidMediaPaintCallback( not_null photo, Fn update); +Fn(Fn)> PaintPreviewCallback( + not_null session, + const Data::CreditsHistoryEntry &entry); + [[nodiscard]] TextWithEntities GenerateEntryName( const Data::CreditsHistoryEntry &entry);