Support correct sitckers for TON gifts.

This commit is contained in:
John Preston 2025-06-30 12:28:44 +04:00
parent 7b870edefa
commit f083180401
13 changed files with 158 additions and 138 deletions

View file

@ -458,7 +458,7 @@ auto GenerateGiftMedia(
.sticker = sticker,
.size = st::chatIntroStickerSize,
.cacheTag = Tag::ChatIntroHelloSticker,
.singleTimePlayback = v::is<GiftTypePremium>(descriptor),
.stopOnLastFrame = v::is<GiftTypePremium>(descriptor),
};
};
push(std::make_unique<StickerInBubblePart>(

View file

@ -16,14 +16,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Stickers {
GiftBoxPack::GiftBoxPack(not_null<Main::Session*> session)
: _session(session)
, _localMonths({ 1, 3, 6, 12, 24 }) {
: _session(session) {
_premium.dividers = { 1, 3, 6, 12, 24 };
_ton.dividers = { 0, 10, 50 };
}
GiftBoxPack::~GiftBoxPack() = default;
rpl::producer<> GiftBoxPack::updated() const {
return _updated.events();
return _premium.updated.events();
}
rpl::producer<> GiftBoxPack::tonUpdated() const {
return _ton.updated.events();
}
int GiftBoxPack::monthsForStars(int stars) const {
@ -36,115 +41,113 @@ int GiftBoxPack::monthsForStars(int stars) const {
}
}
DocumentData *GiftBoxPack::lookup(int months, Type type) const {
const auto it = _setsData.find(type);
if (it == _setsData.end() || it->second.documents.empty()) {
return nullptr;
}
DocumentData *GiftBoxPack::lookup(int months) const {
return lookup(_premium, months, false);
}
const auto& documents = it->second.documents;
const auto itMonths = ranges::lower_bound(_localMonths, months);
const auto fallback = documents[0];
DocumentData *GiftBoxPack::tonLookup(int amount) const {
return lookup(_ton, amount, true);
}
if (itMonths == begin(_localMonths)) {
DocumentData *GiftBoxPack::lookup(
const Pack &pack,
int divider,
bool exact) const {
const auto it = ranges::lower_bound(pack.dividers, divider);
const auto fallback = pack.documents.empty()
? nullptr
: pack.documents.front();
if (it == begin(pack.dividers)) {
return fallback;
} else if (itMonths == end(_localMonths)) {
return documents.back();
} else if (it == end(pack.dividers)) {
return pack.documents.back();
}
const auto left = *(itMonths - 1);
const auto right = *itMonths;
const auto shift = (std::abs(months - left) < std::abs(months - right))
const auto shift = exact
? ((*it > divider) ? 1 : 0)
: (std::abs(divider - (*(it - 1))) < std::abs(divider - (*it)))
? -1
: 0;
const auto index = int(
std::distance(begin(_localMonths), itMonths - shift));
return (index >= documents.size()) ? fallback : documents[index];
const auto index = int(std::distance(begin(pack.dividers), it - shift));
return (index >= pack.documents.size())
? fallback
: pack.documents[index];
}
Data::FileOrigin GiftBoxPack::origin(Type type) const {
const auto it = _setsData.find(type);
if (it == _setsData.end()) {
return Data::FileOrigin();
}
return Data::FileOriginStickerSet(
it->second.setId,
it->second.accessHash);
Data::FileOrigin GiftBoxPack::origin() const {
return Data::FileOriginStickerSet(_premium.id, _premium.accessHash);
}
void GiftBoxPack::load(Type type) {
if (_requestId) {
Data::FileOrigin GiftBoxPack::tonOrigin() const {
return Data::FileOriginStickerSet(_ton.id, _ton.accessHash);
}
void GiftBoxPack::load() {
load(_premium, MTP_inputStickerSetPremiumGifts());
}
void GiftBoxPack::tonLoad() {
load(_ton, MTP_inputStickerSetTonGifts());
}
void GiftBoxPack::load(Pack &pack, const MTPInputStickerSet &set) {
if (pack.requestId || !pack.documents.empty()) {
return;
}
const auto it = _setsData.find(type);
if (it != _setsData.end() && !it->second.documents.empty()) {
return;
}
_requestId = _session->api().request(MTPmessages_GetStickerSet(
type == Type::Currency
? MTP_inputStickerSetTonGifts()
: MTP_inputStickerSetPremiumGifts(),
pack.requestId = _session->api().request(MTPmessages_GetStickerSet(
set,
MTP_int(0) // Hash.
)).done([=](const MTPmessages_StickerSet &result) {
_requestId = 0;
)).done([=, &pack](const MTPmessages_StickerSet &result) {
pack.requestId = 0;
result.match([&](const MTPDmessages_stickerSet &data) {
applySet(data, type);
applySet(pack, data);
}, [](const MTPDmessages_stickerSetNotModified &) {
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
});
}).fail([=] {
_requestId = 0;
}).fail([=, &pack] {
pack.requestId = 0;
}).send();
}
void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data, Type type) {
auto setData = SetData();
setData.setId = data.vset().data().vid().v;
setData.accessHash = data.vset().data().vaccess_hash().v;
void GiftBoxPack::applySet(Pack &pack, const MTPDmessages_stickerSet &data) {
pack.id = data.vset().data().vid().v;
pack.accessHash = data.vset().data().vaccess_hash().v;
auto documents = base::flat_map<DocumentId, not_null<DocumentData*>>();
for (const auto &sticker : data.vdocuments().v) {
const auto document = _session->data().processDocument(sticker);
if (document->sticker()) {
documents.emplace(document->id, document);
if (setData.documents.empty()) {
if (pack.documents.empty()) {
// Fallback.
setData.documents.resize(1);
setData.documents[0] = document;
pack.documents.resize(1);
pack.documents[0] = document;
}
}
}
for (const auto &pack : data.vpacks().v) {
pack.match([&](const MTPDstickerPack &data) {
const auto emoji = qs(data.vemoticon());
if (emoji.isEmpty()) {
return;
}
for (const auto &id : data.vdocuments().v) {
if (const auto document = documents.take(id.v)) {
if (const auto sticker = (*document)->sticker()) {
if (!sticker->alt.isEmpty()) {
const auto ch = int(sticker->alt[0].unicode());
const auto index = (ch - '1'); // [0, 4];
if (index < 0 || index >= _localMonths.size()) {
return;
}
if ((index + 1) > setData.documents.size()) {
setData.documents.resize((index + 1));
}
setData.documents[index] = (*document);
for (const auto &info : data.vpacks().v) {
const auto &data = info.data();
const auto emoji = qs(data.vemoticon());
if (emoji.isEmpty()) {
return;
}
for (const auto &id : data.vdocuments().v) {
if (const auto document = documents.take(id.v)) {
if (const auto sticker = (*document)->sticker()) {
if (!sticker->alt.isEmpty()) {
const auto ch = int(sticker->alt[0].unicode());
const auto index = (ch - '1'); // [0, 4];
if (index < 0 || index >= pack.dividers.size()) {
return;
}
if ((index + 1) > pack.documents.size()) {
pack.documents.resize((index + 1));
}
pack.documents[index] = (*document);
}
}
}
});
}
}
_setsData[type] = std::move(setData);
_updated.fire({});
pack.updated.fire({});
}
} // namespace Stickers

View file

@ -21,38 +21,45 @@ namespace Stickers {
class GiftBoxPack final {
public:
enum class Type {
Gifts,
Currency,
};
explicit GiftBoxPack(not_null<Main::Session*> session);
~GiftBoxPack();
void load(Type type = Type::Gifts);
void load();
[[nodiscard]] int monthsForStars(int stars) const;
[[nodiscard]] DocumentData *lookup(
int months,
Type type = Type::Gifts) const;
[[nodiscard]] Data::FileOrigin origin(Type type = Type::Gifts) const;
[[nodiscard]] DocumentData *lookup(int months) const;
[[nodiscard]] Data::FileOrigin origin() const;
[[nodiscard]] rpl::producer<> updated() const;
void tonLoad();
[[nodiscard]] DocumentData *tonLookup(int amount) const;
[[nodiscard]] Data::FileOrigin tonOrigin() const;
[[nodiscard]] rpl::producer<> tonUpdated() const;
private:
using SetId = uint64;
struct SetData {
SetId setId = 0;
struct Pack {
SetId id = 0;
uint64 accessHash = 0;
std::vector<DocumentData*> documents;
mtpRequestId requestId = 0;
std::vector<int> dividers;
rpl::event_stream<> updated;
};
void applySet(const MTPDmessages_stickerSet &data, Type type);
void load(Pack &pack, const MTPInputStickerSet &set);
void applySet(Pack &pack, const MTPDmessages_stickerSet &data);
[[nodiscard]] DocumentData *lookup(
const Pack &pack,
int divider,
bool exact) const;
const not_null<Main::Session*> _session;
const std::vector<int> _localMonths;
const std::vector<int> _localTonAmounts;
rpl::event_stream<> _updated;
base::flat_map<Type, SetData> _setsData;
mtpRequestId _requestId = 0;
Pack _premium;
Pack _ton;
};

View file

@ -5768,6 +5768,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
auto result = PreparedServiceText();
const auto isSelf = (_from->id == _from->session().userPeerId());
const auto peer = isSelf ? _history->peer : _from;
_history->session().giftBoxStickersPacks().tonLoad();
const auto amount = action.vamount().v;
const auto currency = qs(action.vcurrency());
const auto cost = AmountAndStarCurrency(

View file

@ -48,7 +48,7 @@ auto GenerateGiveawayStart(
return Data{
.sticker = packs.lookup(months),
.size = st::msgServiceGiftBoxStickerSize,
.singleTimePlayback = true,
.stopOnLastFrame = true,
};
};
push(std::make_unique<StickerWithBadgePart>(
@ -222,7 +222,7 @@ auto GenerateGiveawayResults(
.sticker = packs.lookup(emoji, 0),
.skipTop = st::chatGiveawayWinnersTopSkip,
.size = st::maxAnimatedEmojiSize,
.singleTimePlayback = true,
.stopOnLastFrame = true,
};
};
push(std::make_unique<StickerWithBadgePart>(

View file

@ -474,8 +474,8 @@ void StickerInBubblePart::ensureCreated(Element *replacing) const {
_link = data.link;
_skipTop = data.skipTop;
_sticker.emplace(_parent, sticker, skipPremiumEffect, replacing);
if (data.singleTimePlayback) {
_sticker->setPlayingOnce(true);
if (data.stopOnLastFrame) {
_sticker->setStopOnLastFrame(true);
}
_sticker->initSize(data.size);
_sticker->setCustomCachingTag(data.cacheTag);

View file

@ -197,7 +197,7 @@ public:
int skipTop = 0;
int size = 0;
ChatHelpers::StickerLottieSize cacheTag = {};
bool singleTimePlayback = false;
bool stopOnLastFrame = false;
ClickHandlerPtr link;
explicit operator bool() const {

View file

@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "boxes/gift_premium_box.h" // ResolveGiftCode
#include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
@ -30,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h" // GiftedCreditsBox
#include "settings/settings_premium.h" // Settings::ShowGiftPremium
#include "ui/chat/chat_style.h"
#include "ui/controls/ton_common.h" // kNanosInOne
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "window/window_session_controller.h"
@ -416,15 +416,17 @@ void PremiumGift::ensureStickerCreated() const {
if (_sticker) {
return;
} else if (tonGift()) {
const auto document = ChatHelpers::GenerateLocalTgsSticker(
&_parent->history()->session(),
"diamond");
const auto sticker = document->sticker();
Assert(sticker != nullptr);
_sticker.emplace(_parent, document, false, _parent);
_sticker->setPlayingOnce(true);
_sticker->initSize(st::msgServiceStarGiftStickerSize);
_parent->repaint();
const auto &session = _parent->history()->session();
auto &packs = session.giftBoxStickersPacks();
const auto count = _data.count / Ui::kNanosInOne;
if (const auto document = packs.tonLookup(count)) {
if (document->sticker()) {
const auto skipPremiumEffect = false;
_sticker.emplace(_parent, document, skipPremiumEffect, _parent);
_sticker->setStopOnLastFrame(true);
_sticker->initSize(st::msgServiceGiftBoxStickerSize);
}
}
return;
} else if (const auto document = _data.document) {
const auto sticker = document->sticker();
@ -443,7 +445,7 @@ void PremiumGift::ensureStickerCreated() const {
if (document->sticker()) {
const auto skipPremiumEffect = false;
_sticker.emplace(_parent, document, skipPremiumEffect, _parent);
_sticker->setPlayingOnce(true);
_sticker->setStopOnLastFrame(true);
_sticker->initSize(st::msgServiceGiftBoxStickerSize);
}
}

View file

@ -168,7 +168,7 @@ QSize Sticker::countOptimalSize() {
}
bool Sticker::readyToDrawAnimationFrame() {
if (!_lastDiceFrame.isNull()) {
if (!_lastFrameCached.isNull()) {
return true;
}
const auto sticker = _data->sticker();
@ -261,7 +261,7 @@ void Sticker::paintAnimationFrame(
const QRect &r) {
const auto colored = (customEmojiPart() && _data->emojiUsesTextColor())
? ComputeEmojiTextColor(context)
: (context.selected() && !_nextLastDiceFrame)
: (context.selected() && !_nextLastFrame)
? context.st->msgStickerOverlay()->c
: QColor(0, 0, 0, 0);
const auto powerSavingFlag = (emojiSticker() || _diceIndex >= 0)
@ -276,14 +276,16 @@ void Sticker::paintAnimationFrame(
context.now,
paused)
: StickerPlayer::FrameInfo();
if (_nextLastDiceFrame) {
_nextLastDiceFrame = false;
_lastDiceFrame = CacheDiceImage(_diceEmoji, _diceIndex, frame.image);
if (_nextLastFrame) {
_nextLastFrame = false;
_lastFrameCached = (_diceIndex > 0)
? CacheDiceImage(_diceEmoji, _diceIndex, frame.image)
: frame.image;
}
const auto &image = _lastDiceFrame.isNull()
const auto &image = _lastFrameCached.isNull()
? frame.image
: _lastDiceFrame;
const auto prepared = (!_lastDiceFrame.isNull() && context.selected())
: _lastFrameCached;
const auto prepared = (!_lastFrameCached.isNull() && context.selected())
? Images::Colored(
base::duplicate(image),
context.st->msgStickerOverlay()->c)
@ -296,25 +298,25 @@ void Sticker::paintAnimationFrame(
r.y() + (r.height() - size.height()) / 2),
size),
prepared);
if (!_lastDiceFrame.isNull()) {
if (!_lastFrameCached.isNull()) {
return;
}
const auto count = _player->framesCount();
_frameIndex = frame.index;
_framesCount = count;
_nextLastDiceFrame = !paused
&& (_diceIndex > 0)
_nextLastFrame = !paused
&& _stopOnLastFrame
&& (_frameIndex + 2 == count);
const auto playOnce = (_playingOnce || _diceIndex > 0)
const auto playOnce = _playingOnce
? true
: (_diceIndex == 0)
? false
: ((!customEmojiPart() && emojiSticker())
|| !Core::App().settings().loopAnimatedStickers());
const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd();
const auto lastFrame = _stopOnLastFrame && atTheEnd();
const auto switchToNext = !playOnce
|| (!lastDiceFrame && (_frameIndex != 0 || !_oncePlayed));
|| (!lastFrame && (_frameIndex != 0 || !_oncePlayed));
if (!paused
&& switchToNext
&& _player->markFrameShown()
@ -519,12 +521,19 @@ void Sticker::dataMediaCreated() const {
void Sticker::setDiceIndex(const QString &emoji, int index) {
_diceEmoji = emoji;
_diceIndex = index;
_playingOnce = (index >= 0);
_stopOnLastFrame = (index > 0);
}
void Sticker::setPlayingOnce(bool once) {
_playingOnce = once;
}
void Sticker::setStopOnLastFrame(bool stop) {
_stopOnLastFrame = stop;
_playingOnce = true;
}
void Sticker::setCustomCachingTag(ChatHelpers::StickerLottieSize tag) {
_cachingTag = tag;
}
@ -595,8 +604,8 @@ void Sticker::unloadPlayer() {
if (!_player) {
return;
}
if (_diceIndex > 0 && _lastDiceFrame.isNull()) {
_nextLastDiceFrame = false;
if (_stopOnLastFrame && _lastFrameCached.isNull()) {
_nextLastFrame = false;
_oncePlayed = false;
}
_player = nullptr;

View file

@ -67,6 +67,7 @@ public:
void setDiceIndex(const QString &emoji, int index);
void setPlayingOnce(bool once);
void setStopOnLastFrame(bool stop);
void setCustomCachingTag(ChatHelpers::StickerLottieSize tag);
void setCustomEmojiPart();
void setEmojiSticker();
@ -128,7 +129,7 @@ private:
mutable std::shared_ptr<Data::DocumentMedia> _dataMedia;
ClickHandlerPtr _link;
QSize _size;
QImage _lastDiceFrame;
QImage _lastFrameCached;
QString _diceEmoji;
int _diceIndex = -1;
mutable int _frameIndex = -1;
@ -137,12 +138,13 @@ private:
mutable bool _oncePlayed : 1 = false;
mutable bool _premiumEffectPlayed : 1 = false;
mutable bool _premiumEffectSkipped : 1 = false;
mutable bool _nextLastDiceFrame : 1 = false;
mutable bool _nextLastFrame : 1 = false;
bool _skipPremiumEffect : 1 = false;
bool _customEmojiPart : 1 = false;
bool _emojiSticker : 1 = false;
bool _webpagePart : 1 = false;
bool _playingOnce : 1 = false;
bool _stopOnLastFrame : 1 = false;
};

View file

@ -67,7 +67,6 @@ QString ToUsd(
int afterFloat) {
constexpr auto kApproximately = QChar(0x2248);
const auto result = int64(base::SafeRound(value.value() * rate));
return QString(kApproximately)
+ QChar('$')
+ QString::number(

View file

@ -121,8 +121,7 @@ Credits::Credits(
st::tonFieldIconSize,
st::currencyFg->c)
: Ui::GenerateStars(st::creditsBalanceStarHeight, 1)) {
_controller->session().giftBoxStickersPacks().load(
Stickers::GiftBoxPack::Type::Currency);
_controller->session().giftBoxStickersPacks().tonLoad();
setupContent();
_controller->session().premiumPossibleValue(
@ -418,7 +417,6 @@ void Credits::setupContent() {
};
const auto state = content->lifetime().make_state<State>();
const auto paddings = rect::m::sum::h(st::boxRowPadding);
{
const auto button = content->add(
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
@ -623,13 +621,13 @@ QPointer<Ui::RpWidget> Credits::createPinnedToTop(
st::creditsPremiumCover,
Ui::Premium::TopBarDescriptor{
.clickContextOther = clickContextOther,
.logo = isCurrency ? u"diamond"_q : QString(),
.title = title(),
.about = (isCurrency
? tr::lng_credits_currency_summary_about
: tr::lng_credits_summary_about)(
TextWithEntities::Simple),
.light = true,
.logo = isCurrency ? u"diamond"_q : QString(),
.gradientStops = Ui::Premium::CreditsIconGradientStops(),
});
}();

View file

@ -1311,13 +1311,12 @@ void GenericCreditsEntryBox(
auto &packs = session->giftBoxStickersPacks();
const auto document = starGiftSticker
? starGiftSticker
: e.credits.ton()
? packs.tonLookup(e.credits.whole())
: packs.lookup(
e.premiumMonthsForStars
? e.premiumMonthsForStars
: packs.monthsForStars(e.credits.whole()),
e.credits.stars()
? Stickers::GiftBoxPack::Type::Gifts
: Stickers::GiftBoxPack::Type::Currency);
: packs.monthsForStars(e.credits.whole()));
if (document && document->sticker()) {
state->sticker = document;
state->media = document->createMediaView();