diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 138c2850c2..a53f8e2c1e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1662,6 +1662,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat."; "lng_group_stickers_add" = "Choose sticker set"; "lng_premium_stickers" = "Premium stickers"; +"lng_premium_reaction_no_group" = "This reaction is not available in this group."; +"lng_premium_reaction_no_channel" = "This reaction is not available in this channel."; "lng_premium" = "Premium"; "lng_premium_free" = "Free"; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index c3b9eaa071..38882d42a3 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -42,13 +42,13 @@ constexpr auto kEnumerateCount = 3; struct Descriptor { PremiumPreview section = PremiumPreview::Stickers; DocumentData *requestedSticker = nullptr; - base::flat_set disabledReactions; + base::flat_map disabled; }; bool operator==(const Descriptor &a, const Descriptor &b) { return (a.section == b.section) && (a.requestedSticker == b.requestedSticker) - && (a.disabledReactions == b.disabledReactions); + && (a.disabled == b.disabled); } bool operator!=(const Descriptor &a, const Descriptor &b) { @@ -201,17 +201,20 @@ public: ReactionPreview( not_null controller, const Data::Reaction &reaction, + ReactionDisableType type, Fn update); [[nodiscard]] bool playsEffect() const; - void paint(QPainter &p, int x, int y, float64 scale); + void paint(Painter &p, int x, int y, float64 scale); void paintEffect(QPainter &p, int x, int y, float64 scale); + void paintRestricted(Painter &p, int x, int bottom, float64 scale); void startAnimations(); void cancelAnimations(); private: void checkReady(); + void paintTitle(Painter &p, int x, int y, float64 scale); const not_null _controller; const Fn _update; @@ -220,6 +223,8 @@ private: std::unique_ptr _center; std::unique_ptr _around; std::unique_ptr _pathGradient; + Ui::Text::String _name; + Ui::Text::String _disabled; QImage _cache1; QImage _cache2; QImage _cache3; @@ -230,9 +235,20 @@ private: }; +[[nodiscard]] QString DisabledText(ReactionDisableType type) { + switch (type) { + case ReactionDisableType::Group: + return tr::lng_premium_reaction_no_group(tr::now); + case ReactionDisableType::Channel: + return tr::lng_premium_reaction_no_channel(tr::now); + } + return QString(); +} + ReactionPreview::ReactionPreview( not_null controller, const Data::Reaction &reaction, + ReactionDisableType type, Fn update) : _controller(controller) , _update(std::move(update)) @@ -241,7 +257,9 @@ ReactionPreview::ReactionPreview( , _pathGradient( HistoryView::MakePathShiftGradient( controller->chatStyle(), - _update)) { + _update)) +, _name(st::premiumReactionName, reaction.title) +, _disabled(st::defaultTextStyle, DisabledText(type)) { _centerMedia->checkStickerLarge(); _aroundMedia->checkStickerLarge(); checkReady(); @@ -258,7 +276,7 @@ void ReactionPreview::checkReady() { nullptr, ChatHelpers::StickerLottieSize::PremiumReactionPreview, QSize(size, size) * style::DevicePixelRatio(), - Lottie::Quality::High); + Lottie::Quality::Default); result->updates() | rpl::start_with_next(_update, _lifetime); return result; }; @@ -282,7 +300,7 @@ void ReactionPreview::cancelAnimations() { _playRequested = false; } -void ReactionPreview::paint(QPainter &p, int x, int y, float64 scale) { +void ReactionPreview::paint(Painter &p, int x, int y, float64 scale) { const auto size = st::premiumReactionAround; const auto center = st::premiumReactionSize; const auto inner = QRect( @@ -357,6 +375,54 @@ void ReactionPreview::paint(QPainter &p, int x, int y, float64 scale) { if (useScale) { p.restore(); } + paintTitle(p, x, y, scale); +} + +void ReactionPreview::paintTitle(Painter &p, int x, int y, float64 scale) { + const auto first = st::premiumReactionScale1; + if (scale <= first) { + return; + } + const auto opacity = (scale - first) / (1. - first); + p.setOpacity(opacity * 0.2); + auto hq = PainterHighQualityEnabler(p); + const auto width = _name.maxWidth(); + const auto sticker = st::premiumReactionAround; + const auto inner = QRect( + x + (sticker - width) / 2, + y + (sticker / 2) + st::premiumReactionNameTop, + width, + st::premiumReactionName.font->height); + const auto outer = inner.marginsAdded(st::premiumReactionNamePadding); + const auto radius = outer.height() / 2; + p.setPen(Qt::NoPen); + p.setBrush(st::premiumButtonFg); + p.drawRoundedRect(outer, radius, radius); + p.setOpacity(opacity); + p.setPen(st::premiumButtonFg); + _name.draw(p, inner.x(), inner.y(), width); + if (!_disabled.isEmpty()) { + const auto left = x + (sticker / 2) - (_disabled.maxWidth() / 2); + } + p.setOpacity(1.); +} + +void ReactionPreview::paintRestricted( + Painter &p, + int x, + int bottom, + float64 scale) { + const auto first = st::premiumReactionScale1; + if (scale <= first || _disabled.isEmpty()) { + return; + } + const auto sticker = st::premiumReactionAround; + const auto opacity = (scale - first) / (1. - first); + p.setOpacity(opacity); + p.setPen(st::premiumButtonFg); + const auto left = x + (sticker / 2) - (_disabled.maxWidth() / 2); + _disabled.draw(p, left, bottom - 2.5 * st::normalFont->height, _disabled.maxWidth()); + p.setOpacity(1.); } bool ReactionPreview::playsEffect() const { @@ -389,7 +455,8 @@ void ReactionPreview::paintEffect(QPainter &p, int x, int y, float64 scale) { [[nodiscard]] not_null ReactionsPreview( not_null parent, - not_null controller) { + not_null controller, + const base::flat_map &disabled) { struct State { std::vector> entries; Ui::Animations::Simple shifting; @@ -417,9 +484,11 @@ void ReactionPreview::paintEffect(QPainter &p, int x, int y, float64 scale) { || !reaction.aroundAnimation) { continue; } + const auto i = disabled.find(reaction.emoji); state->entries.push_back(std::make_unique( controller, reaction, + (i != end(disabled)) ? i->second : ReactionDisableType::None, [=] { result->update(); })); } const auto enumerate = [=]( @@ -485,16 +554,15 @@ void ReactionPreview::paintEffect(QPainter &p, int x, int y, float64 scale) { if (entry->playsEffect() || (paintedRight > 0 && paintedLeft < st::boxWideWidth)) { callback(entry, left, scale, delta + index); - } else { - int a = 0; } } }; result->paintRequest( ) | rpl::start_with_next([=] { - auto p = QPainter(result); - const auto top = result->height() / 2 - st::premiumReactionTop; + auto p = Painter(result); + const auto bottom = result->height(); + const auto top = (bottom / 2) - st::premiumReactionTop; auto effects = std::vector>(); if (!state->played && !state->shifting.animating()) { state->played = true; @@ -509,6 +577,7 @@ void ReactionPreview::paintEffect(QPainter &p, int x, int y, float64 scale) { float64 scale, int index) { entry->paint(p, left, top, scale); + entry->paintRestricted(p, left, bottom, scale); if (entry->playsEffect()) { effects.push_back([=, &p] { entry->paintEffect(p, left, top, scale); @@ -629,7 +698,10 @@ void StickerBox( state->content = StickerPreview(outer, controller, media); break; case PremiumPreview::Reactions: - state->content = ReactionsPreview(outer, controller); + state->content = ReactionsPreview( + outer, + controller, + descriptor.disabled); break; case PremiumPreview::Avatars: break; @@ -774,9 +846,9 @@ void ShowStickerPreviewBox( void ShowPremiumPreviewBox( not_null controller, PremiumPreview section, - const base::flat_set &disabledReactions) { + const base::flat_map &disabled) { Show(controller, Descriptor{ .section = section, - .disabledReactions = disabledReactions, + .disabled = disabled, }); } diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index ad9a847d63..2226209e46 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -22,8 +22,13 @@ enum class PremiumPreview { Stickers, Avatars, }; +enum class ReactionDisableType { + None, + Group, + Channel, +}; void ShowPremiumPreviewBox( not_null controller, PremiumPreview section, - const base::flat_set &disabledReactions = {}); + const base::flat_map &disabled = {}); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 4e7c893557..6f5579c547 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -310,4 +310,10 @@ premiumReactionSkip2: 78px; premiumReactionScale2: 0.62; premiumReactionSkip3: 64px; premiumReactionScale3: 0.5; - +premiumReactionName: TextStyle(defaultTextStyle) { + font: font(15px semibold); + linkFont: font(15px semibold); + linkFontOver: font(15px semibold); +} +premiumReactionNameTop: 64px; +premiumReactionNamePadding: margins(12px, 4px, 12px, 6px); diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 39218ac428..80d62cd406 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -19,6 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_cloud_themes.h" #include "data/data_message_reactions.h" +#include "data/data_peer_values.h" +#include "history/history.h" #include "history/history_item.h" #include "settings/settings_premium.h" #include "main/main_session.h" @@ -341,6 +343,24 @@ bool ShowSendPremiumError( return true; } +[[nodiscard]] auto ExtractDisabledReactions( + not_null peer, + const std::vector &list) +-> base::flat_map { + auto result = base::flat_map(); + const auto type = peer->isBroadcast() + ? ReactionDisableType::Channel + : ReactionDisableType::Group; + if (const auto allowed = Data::PeerAllowedReactions(peer)) { + for (const auto &reaction : list) { + if (reaction.premium && !allowed->contains(reaction.emoji)) { + result.emplace(reaction.emoji, type); + } + } + } + return result; +} + bool ShowReactPremiumError( not_null controller, not_null item, @@ -358,7 +378,7 @@ bool ShowReactPremiumError( ShowPremiumPreviewBox( controller, PremiumPreview::Reactions, - {}); + ExtractDisabledReactions(item->history()->peer, list)); return true; }