Show first paid media in transactions history.

This commit is contained in:
John Preston 2024-06-21 18:10:04 +04:00
parent 57254ca259
commit 1399d2501d
8 changed files with 200 additions and 40 deletions

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -28,9 +29,40 @@ constexpr auto kTransactionsLimit = 100;
const MTPStarsTransaction &tl, const MTPStarsTransaction &tl,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
using HistoryPeerTL = MTPDstarsTransactionPeer; using HistoryPeerTL = MTPDstarsTransactionPeer;
using namespace Data;
const auto owner = &peer->owner();
const auto photo = tl.data().vphoto() const auto photo = tl.data().vphoto()
? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation()) ? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
: nullptr; : nullptr;
auto extended = std::vector<CreditsHistoryEntry::Media>();
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 auto barePeerId = tl.data().vpeer().match([](
const HistoryPeerTL &p) { const HistoryPeerTL &p) {
return peerFromMTP(p.vpeer()); return peerFromMTP(p.vpeer());
@ -53,6 +85,7 @@ constexpr auto kTransactionsLimit = 100;
.description = qs(tl.data().vdescription().value_or_empty()), .description = qs(tl.data().vdescription().value_or_empty()),
.date = base::unixtime::parse(tl.data().vdate().v), .date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0, .photoId = photo ? photo->id : 0,
.extended = std::move(extended),
.credits = tl.data().vstars().v, .credits = tl.data().vstars().v,
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()), .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = barePeerId, .barePeerId = barePeerId,

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_media_prepare.h" #include "storage/storage_media_prepare.h"
#include "iv/iv_instance.h" #include "iv/iv_instance.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "mtproto/mtproto_config.h" #include "mtproto/mtproto_config.h"
@ -64,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kMaxMessageLength = 4096; constexpr auto kMaxMessageLength = 4096;
constexpr auto kMaxPrice = 1000ULL;
using Ui::SendFilesWay; using Ui::SendFilesWay;
@ -125,6 +125,9 @@ void EditPriceBox(
0, 0,
st::defaultSubsectionTitlePadding.right(), st::defaultSubsectionTitlePadding.right(),
0))); 0)));
const auto limit = session->appConfig().get<int>(
u"stars_paid_post_amount_max"_q,
10'000);
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>( const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
box, box,
st::editTagField.heightMin)); st::editTagField.heightMin));
@ -133,7 +136,7 @@ void EditPriceBox(
st::editTagField, st::editTagField,
tr::lng_paid_cost_placeholder(), tr::lng_paid_cost_placeholder(),
price ? QString::number(price) : QString(), price ? QString::number(price) : QString(),
kMaxPrice); limit);
const auto field = owned.data(); const auto field = owned.data();
wrap->widthValue() | rpl::start_with_next([=](int width) { wrap->widthValue() | rpl::start_with_next([=](int width) {
field->move(0, 0); field->move(0, 0);
@ -167,7 +170,7 @@ void EditPriceBox(
const auto save = [=] { const auto save = [=] {
const auto now = field->getLastText().toULongLong(); const auto now = field->getLastText().toULongLong();
if (now > kMaxPrice) { if (now > limit) {
field->showError(); field->showError();
return; return;
} }

View file

@ -30,11 +30,21 @@ 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;
uint64 credits = 0; uint64 credits = 0;
uint64 bareMsgId = 0; uint64 bareMsgId = 0;
uint64 barePeerId = 0; uint64 barePeerId = 0;

View file

@ -756,20 +756,24 @@ private:
QString _name; QString _name;
Ui::Text::String _rightText; Ui::Text::String _rightText;
base::has_weak_ptr _guard;
}; };
CreditsRow::CreditsRow(not_null<PeerData*> peer, const Descriptor &descriptor) CreditsRow::CreditsRow(
not_null<PeerData*> peer,
const Descriptor &descriptor)
: PeerListRow(peer, UniqueRowIdFromEntry(descriptor.entry)) : PeerListRow(peer, UniqueRowIdFromEntry(descriptor.entry))
, _entry(descriptor.entry) , _entry(descriptor.entry)
, _creditIcon(descriptor.creditIcon) , _creditIcon(descriptor.creditIcon)
, _rowHeight(descriptor.rowHeight) { , _rowHeight(descriptor.rowHeight) {
const auto photo = _entry.photoId const auto callback = Ui::PaintPreviewCallback(
? peer->session().data().photo(_entry.photoId).get() &peer->session(),
: nullptr; _entry);
if (photo) { if (callback) {
_paintUserpicCallback = Ui::GenerateCreditsPaintEntryCallback( _paintUserpicCallback = callback(crl::guard(&_guard, [this, update = descriptor.updateCallback] {
photo, update(this);
[this, update = descriptor.updateCallback] { update(this); }); }));
} }
init(); init();
} }

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/gift_premium_box.h" #include "boxes/gift_premium_box.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/data_document.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "core/click_handler_types.h" // UrlClickHandler #include "core/click_handler_types.h" // UrlClickHandler
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
@ -350,18 +351,16 @@ void ReceiptCreditsBox(
using Type = Data::CreditsHistoryEntry::PeerType; using Type = Data::CreditsHistoryEntry::PeerType;
const auto &stUser = st::boostReplaceUserpic; const auto &stUser = st::boostReplaceUserpic;
const auto session = &controller->session();
const auto peer = (e.peerType == Type::PremiumBot) const auto peer = (e.peerType == Type::PremiumBot)
? premiumBot ? premiumBot
: e.barePeerId : e.barePeerId
? controller->session().data().peer(PeerId(e.barePeerId)).get() ? session->data().peer(PeerId(e.barePeerId)).get()
: nullptr; : nullptr;
const auto photo = e.photoId if (const auto callback = Ui::PaintPreviewCallback(session, e)) {
? controller->session().data().photo(e.photoId).get()
: nullptr;
if (photo) {
content->add(object_ptr<Ui::CenterWrap<>>( content->add(object_ptr<Ui::CenterWrap<>>(
content, content,
HistoryEntryPhoto(content, photo, stUser.photoSize))); GenericEntryPhoto(content, callback, stUser.photoSize)));
} else if (peer) { } else if (peer) {
content->add(object_ptr<Ui::CenterWrap<>>( content->add(object_ptr<Ui::CenterWrap<>>(
content, content,
@ -539,18 +538,16 @@ void ReceiptCreditsBox(
}, button->lifetime()); }, button->lifetime());
} }
object_ptr<Ui::RpWidget> HistoryEntryPhoto( object_ptr<Ui::RpWidget> GenericEntryPhoto(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo, Fn<Fn<void(Painter &, int, int, int, int)>(Fn<void()>)> callback,
int photoSize) { int photoSize) {
auto owned = object_ptr<Ui::RpWidget>(parent); auto owned = object_ptr<Ui::RpWidget>(parent);
const auto widget = owned.data(); const auto widget = owned.data();
widget->resize(Size(photoSize)); widget->resize(Size(photoSize));
const auto draw = Ui::GenerateCreditsPaintEntryCallback( const auto draw = callback(
photo, crl::guard(widget, [=] { widget->update(); }));
[=] { widget->update(); });
widget->paintRequest( widget->paintRequest(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
auto p = Painter(widget); auto p = Painter(widget);
@ -560,25 +557,40 @@ object_ptr<Ui::RpWidget> HistoryEntryPhoto(
return owned; return owned;
} }
object_ptr<Ui::RpWidget> HistoryEntryPhoto(
not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo,
int photoSize) {
return GenericEntryPhoto(
parent,
[=](Fn<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(photo, update);
},
photoSize);
}
object_ptr<Ui::RpWidget> HistoryEntryVideo(
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( object_ptr<Ui::RpWidget> PaidMediaPhoto(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
int photoSize) { int photoSize) {
auto owned = object_ptr<Ui::RpWidget>(parent); return GenericEntryPhoto(
const auto widget = owned.data(); parent,
widget->resize(Size(photoSize)); [=](Fn<void()> update) {
return Ui::GeneratePaidMediaPaintCallback(photo, update);
const auto draw = Ui::GeneratePaidMediaPaintCallback( },
photo, photoSize);
[=] { widget->update(); });
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
draw(p, 0, 0, photoSize, photoSize);
}, widget->lifetime());
return owned;
} }
void SmallBalanceBox( void SmallBalanceBox(

View file

@ -55,11 +55,21 @@ void ReceiptCreditsBox(
PeerData *premiumBot, PeerData *premiumBot,
const Data::CreditsHistoryEntry &e); const Data::CreditsHistoryEntry &e);
[[nodiscard]] object_ptr<Ui::RpWidget> GenericEntryPhoto(
not_null<Ui::RpWidget*> parent,
Fn<Fn<void(Painter &, int, int, int, int)>(Fn<void()> update)> callback,
int photoSize);
[[nodiscard]] object_ptr<Ui::RpWidget> HistoryEntryPhoto( [[nodiscard]] object_ptr<Ui::RpWidget> HistoryEntryPhoto(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
int photoSize); int photoSize);
[[nodiscard]] object_ptr<Ui::RpWidget> HistoryEntryVideo(
not_null<Ui::RpWidget*> parent,
not_null<DocumentData*> video,
int photoSize);
[[nodiscard]] object_ptr<Ui::RpWidget> PaidMediaPhoto( [[nodiscard]] object_ptr<Ui::RpWidget> PaidMediaPhoto(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo, not_null<PhotoData*> photo,

View file

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_credits.h" #include "data/data_credits.h"
#include "data/data_file_origin.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.h"
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -232,7 +234,53 @@ Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
minSize, minSize,
minSize), minSize),
size * style::DevicePixelRatio(), size * style::DevicePixelRatio(),
{ .options = Images::Option::RoundCircle }); { .options = Images::Option::RoundLarge });
}
p.drawImage(x, y, state->image);
};
}
Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
not_null<DocumentData*> video,
Fn<void()> update) {
struct State {
std::shared_ptr<Data::DocumentMedia> view;
Image *imagePtr = nullptr;
QImage image;
rpl::lifetime downloadLifetime;
bool entryImageLoaded = false;
};
const auto state = std::make_shared<State>();
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); p.drawImage(x, y, state->image);
}; };
@ -282,6 +330,33 @@ Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback(
}; };
} }
Fn<Fn<void(Painter&, int, int, int, int)>(Fn<void()>)> PaintPreviewCallback(
not_null<Main::Session*> 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<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(photo, update);
};
} else if (video) {
return [=](Fn<void()> update) {
return Ui::GenerateCreditsPaintEntryCallback(video, update);
};
}
return nullptr;
}
TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) {
return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment)
? tr::lng_bot_username_description1_link ? tr::lng_bot_username_description1_link

View file

@ -8,11 +8,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
class PhotoData; class PhotoData;
class DocumentData;
namespace Data { namespace Data {
struct CreditsHistoryEntry; struct CreditsHistoryEntry;
} // namespace Data } // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Ui { namespace Ui {
class MaskedInputField; class MaskedInputField;
class RpWidget; class RpWidget;
@ -38,10 +43,18 @@ Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Fn<void()> update); Fn<void()> update);
Fn<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
not_null<DocumentData*> video,
Fn<void()> update);
Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback( Fn<void(Painter &, int, int, int, int)> GeneratePaidMediaPaintCallback(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
Fn<void()> update); Fn<void()> update);
Fn<Fn<void(Painter&, int, int, int, int)>(Fn<void()>)> PaintPreviewCallback(
not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &entry);
[[nodiscard]] TextWithEntities GenerateEntryName( [[nodiscard]] TextWithEntities GenerateEntryName(
const Data::CreditsHistoryEntry &entry); const Data::CreditsHistoryEntry &entry);