diff --git a/Telegram/Resources/icons/payments/small_star.png b/Telegram/Resources/icons/payments/small_star.png new file mode 100644 index 000000000..94e3fc4c5 Binary files /dev/null and b/Telegram/Resources/icons/payments/small_star.png differ diff --git a/Telegram/Resources/icons/payments/small_star@2x.png b/Telegram/Resources/icons/payments/small_star@2x.png new file mode 100644 index 000000000..4dc7016fe Binary files /dev/null and b/Telegram/Resources/icons/payments/small_star@2x.png differ diff --git a/Telegram/Resources/icons/payments/small_star@3x.png b/Telegram/Resources/icons/payments/small_star@3x.png new file mode 100644 index 000000000..8f7ae06da Binary files /dev/null and b/Telegram/Resources/icons/payments/small_star@3x.png differ diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index cd2cefacd..39d7983d7 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -285,7 +285,7 @@ void SendCreditsBox( lt_count, rpl::single(form->invoice.amount) | tr::to_count(), lt_emoji, - rpl::single(CreditsEmoji(session)), + rpl::single(CreditsEmojiSmall(session)), Ui::Text::RichLangValue); const auto buttonLabel = Ui::CreateChild( button, @@ -369,4 +369,13 @@ TextWithEntities CreditsEmoji(not_null session) { QString(QChar(0x2B50))); } +TextWithEntities CreditsEmojiSmall(not_null session) { + return Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::starIconSmall, + st::starIconSmallPadding, + true), + QString(QChar(0x2B50))); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index 71675c720..c0107a360 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -29,4 +29,7 @@ void SendCreditsBox( [[nodiscard]] TextWithEntities CreditsEmoji( not_null session); +[[nodiscard]] TextWithEntities CreditsEmojiSmall( + not_null session); + } // namespace Ui diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 7d0025e8c..72ca05542 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2068,7 +2068,7 @@ ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const { : parent()->originalText(); const auto hasMiniImages = !images.empty(); auto nice = Ui::Text::Colorized( - Ui::CreditsEmoji(&parent()->history()->session())); + Ui::CreditsEmojiSmall(&parent()->history()->session())); nice.append(WithCaptionNotificationText(type, caption, hasMiniImages)); return { .text = std::move(nice), diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index b85001331..50f5b7454 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -721,6 +721,14 @@ void Element::overrideMedia(std::unique_ptr media) { _flags |= Flag::MediaOverriden; } +not_null Element::enforcePurchasedTag() { + if (const auto purchased = Get()) { + return purchased; + } + AddComponents(PurchasedTag::Bit()); + return Get(); +} + void Element::refreshMedia(Element *replacing) { if (_flags & Flag::MediaOverriden) { return; diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index d4139e8ec..1d52d7925 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -278,6 +278,10 @@ struct FakeBotAboutTop : public RuntimeComponent { int height = 0; }; +struct PurchasedTag : public RuntimeComponent { + Ui::Text::String text; +}; + struct TopicButton { std::unique_ptr ripple; ClickHandlerPtr link; @@ -564,6 +568,8 @@ public: void overrideMedia(std::unique_ptr media); + [[nodiscard]] not_null enforcePurchasedTag(); + virtual bool consumeHorizontalScroll(QPoint position, int delta) { return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 20748d9fa..ffe0d7757 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -191,6 +191,8 @@ Gif::Gif( } } ensureTranscribeButton(); + + _purchasedPriceTag = hasPurchasedTag(); } Gif::~Gif() { @@ -663,6 +665,10 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto skipDrawingSurrounding = context.skipDrawingParts == PaintContext::SkipDrawingParts::Surrounding; + if (!skipDrawingSurrounding && _purchasedPriceTag) { + drawPurchasedTag(p, rthumb, context); + } + if (!unwrapped && !skipDrawingSurrounding) { if (!isRound || !inWebPage) { drawCornerStatus(p, context, QPoint()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index f0460bf0d..f86421f92 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -213,6 +213,7 @@ private: mutable bool _thumbCacheBlurred : 1 = false; mutable bool _thumbIsEllipse : 1 = false; mutable bool _pollingStory : 1 = false; + mutable bool _purchasedPriceTag : 1 = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 4f8c8096b..28e4e0870 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_media.h" +#include "boxes/send_credits_box.h" // CreditsEmoji. #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" @@ -18,12 +19,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" #include "data/data_web_page.h" +#include "lang/lang_tag.h" // FormatCountDecimal. #include "ui/item_text_options.h" #include "ui/chat/chat_style.h" #include "ui/chat/message_bubble.h" #include "ui/effects/spoiler_mess.h" #include "ui/image/image_prepare.h" +#include "ui/cached_round_corners.h" +#include "ui/painter.h" #include "ui/power_saving.h" +#include "ui/text/text_utilities.h" #include "core/ui_integration.h" #include "styles/style_chat.h" @@ -202,6 +207,72 @@ QSize Media::countCurrentSize(int newWidth) { return QSize(qMin(newWidth, maxWidth()), minHeight()); } +bool Media::hasPurchasedTag() const { + if (const auto media = parent()->data()->media()) { + if (const auto invoice = media->invoice()) { + if (invoice->isPaidMedia && !invoice->extendedMedia.empty()) { + const auto photo = invoice->extendedMedia.front()->photo(); + return !photo || !photo->extendedMediaPreview(); + } + } + } + return false; +} + +void Media::drawPurchasedTag( + Painter &p, + QRect outer, + const PaintContext &context) const { + const auto purchased = parent()->enforcePurchasedTag(); + if (purchased->text.isEmpty()) { + const auto item = parent()->data(); + const auto media = item->media(); + const auto invoice = media ? media->invoice() : nullptr; + const auto amount = invoice ? invoice->amount : 0; + if (!amount) { + return; + } + const auto session = &item->history()->session(); + auto text = Ui::Text::Colorized(Ui::CreditsEmojiSmall(session)); + text.append(Lang::FormatCountDecimal(amount)); + purchased->text.setMarkedText(st::defaultTextStyle, text, kMarkupTextOptions, Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [] {}, + }); + } + + const auto st = context.st; + const auto sti = context.imageStyle(); + auto right = outer.x() + outer.width(); + auto top = outer.y(); + right -= st::msgDateImgDelta + st::msgDateImgPadding.x(); + top += st::msgDateImgDelta + st::msgDateImgPadding.y(); + + const auto size = QSize( + purchased->text.maxWidth(), + st::normalFont->height); + const auto tagX = right - size.width(); + const auto tagY = top; + const auto tagW = size.width() + 2 * st::msgDateImgPadding.x(); + const auto tagH = size.height() + 2 * st::msgDateImgPadding.y(); + Ui::FillRoundRect( + p, + tagX - st::msgDateImgPadding.x(), + tagY - st::msgDateImgPadding.y(), + tagW, + tagH, + sti->msgDateImgBg, + sti->msgDateImgBgCorners); + + p.setPen(st->msgDateImgFg()); + purchased->text.draw(p, { + .position = { tagX, tagY }, + .outerWidth = width(), + .availableWidth = tagW - 2 * st::msgDateImgPadding.x(), + .palette = &st->priceTagTextPalette(), + }); +} + void Media::fillImageShadow( QPainter &p, QRect rect, diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 9d577255c..94ad6520b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -357,6 +357,12 @@ public: return false; } + [[nodiscard]] bool hasPurchasedTag() const; + void drawPurchasedTag( + Painter &p, + QRect outer, + const PaintContext &context) const; + virtual ~Media() = default; protected: diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 16d9d8080..4b26222ee 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -133,6 +133,8 @@ GroupedMedia::Mode GroupedMedia::DetectMode(not_null media) { } QSize GroupedMedia::countOptimalSize() { + _purchasedPriceTag = hasPurchasedTag(); + std::vector sizes; const auto partsCount = _parts.size(); sizes.reserve(partsCount); @@ -433,7 +435,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { if (!part.cache.isNull()) { nowCache = true; } - if (unpaid) { + if (unpaid || _purchasedPriceTag) { fullRect = fullRect.united(part.geometry); } } @@ -445,6 +447,8 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { unpaid->drawPriceTag(p, fullRect, context, [&] { return generatePriceTagBackground(fullRect); }); + } else if (_purchasedPriceTag) { + drawPurchasedTag(p, fullRect, context); } // date diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index b101fef91..5397b6817 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -155,7 +155,8 @@ private: mutable std::optional _captionItem; std::vector _parts; Mode _mode = Mode::Grid; - bool _needBubble = false; + bool _needBubble : 1 = false; + bool _purchasedPriceTag : 1 = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 2485552d8..043883035 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -139,6 +139,7 @@ void Photo::create(FullMsgId contextId, PeerData *chat) { if (_spoiler) { createSpoilerLink(_spoiler.get()); } + _purchasedPriceTag = hasPurchasedTag() ? 1 : 0; } void Photo::ensureDataMediaCreated() const { @@ -393,6 +394,14 @@ void Photo::draw(Painter &p, const PaintContext &context) const { p.drawRoundedRect(rect, radius, radius); sti->historyPageEnlarge.paintInCenter(p, rect); } + if (_purchasedPriceTag) { + auto geometry = rthumb; + if (showEnlarge) { + const auto rect = enlargeRect(); + geometry.setY(rect.y() + rect.height()); + } + drawPurchasedTag(p, geometry, context); + } // date if (!inWebPage && (!bubble || isBubbleBottom())) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 223eae37e..3c1aec3e4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -167,7 +167,8 @@ private: const std::unique_ptr _spoiler; mutable QImage _imageCache; mutable std::optional _imageCacheRounding; - uint32 _serviceWidth : 28 = 0; + uint32 _serviceWidth : 27 = 0; + uint32 _purchasedPriceTag : 1 = 0; mutable uint32 _imageCacheForum : 1 = 0; mutable uint32 _imageCacheBlurred : 1 = 0; mutable uint32 _pollingStory : 1 = 0; diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 1518b69b1..acfde082f 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -44,3 +44,6 @@ creditsBoxAboutDivider: FlatLabel(boxDividerLabel) { creditsBoxButtonLabel: FlatLabel(defaultFlatLabel) { style: semiboldTextStyle; } + +starIconSmall: icon{{ "payments/small_star", windowFg }}; +starIconSmallPadding: margins(0px, -3px, 0px, 0px);