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 "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<PeerData*> 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<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 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,

View file

@ -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<int>(
u"stars_paid_post_amount_max"_q,
10'000);
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
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;
}

View file

@ -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<Media> extended;
uint64 credits = 0;
uint64 bareMsgId = 0;
uint64 barePeerId = 0;

View file

@ -756,20 +756,24 @@ private:
QString _name;
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))
, _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();
}

View file

@ -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<Ui::CenterWrap<>>(
content,
HistoryEntryPhoto(content, photo, stUser.photoSize)));
GenericEntryPhoto(content, callback, stUser.photoSize)));
} else if (peer) {
content->add(object_ptr<Ui::CenterWrap<>>(
content,
@ -539,18 +538,16 @@ void ReceiptCreditsBox(
}, button->lifetime());
}
object_ptr<Ui::RpWidget> HistoryEntryPhoto(
object_ptr<Ui::RpWidget> GenericEntryPhoto(
not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo,
Fn<Fn<void(Painter &, int, int, int, int)>(Fn<void()>)> callback,
int photoSize) {
auto owned = object_ptr<Ui::RpWidget>(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<Ui::RpWidget> HistoryEntryPhoto(
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(
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;
return GenericEntryPhoto(
parent,
[=](Fn<void()> update) {
return Ui::GeneratePaidMediaPaintCallback(photo, update);
},
photoSize);
}
void SmallBalanceBox(

View file

@ -55,11 +55,21 @@ void ReceiptCreditsBox(
PeerData *premiumBot,
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(
not_null<Ui::RpWidget*> parent,
not_null<PhotoData*> photo,
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(
not_null<Ui::RpWidget*> parent,
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_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<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
minSize,
minSize),
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);
};
@ -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) {
return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment)
? tr::lng_bot_username_description1_link

View file

@ -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<void(Painter &, int, int, int, int)> GenerateCreditsPaintEntryCallback(
not_null<PhotoData*> photo,
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(
not_null<PhotoData*> photo,
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(
const Data::CreditsHistoryEntry &entry);