From 5cc6275fc3fb9be28e5c5414b4107bae94626279 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Sep 2022 20:53:42 +0400 Subject: [PATCH] Fly + effects when choosing an emoji status. --- Telegram/CMakeLists.txt | 4 + Telegram/Resources/langs/lang.strings | 7 +- .../boxes/peers/edit_contact_box.cpp | 1 + .../SourceFiles/boxes/premium_preview_box.cpp | 472 +----------------- .../SourceFiles/boxes/premium_preview_box.h | 14 +- .../boxes/reactions_settings_box.cpp | 4 +- .../chat_helpers/emoji_list_widget.cpp | 29 +- .../data/stickers/data_custom_emoji.h | 16 +- .../history/history_inner_widget.cpp | 16 +- .../history/history_inner_widget.h | 1 - .../history/view/history_view_bottom_info.cpp | 29 +- .../history/view/history_view_bottom_info.h | 24 +- .../history/view/history_view_element.cpp | 2 +- .../history/view/history_view_element.h | 19 +- .../history/view/history_view_list_widget.cpp | 8 +- .../history/view/history_view_message.cpp | 2 +- .../history/view/history_view_message.h | 2 +- .../view/reactions/history_view_reactions.cpp | 11 +- .../view/reactions/history_view_reactions.h | 4 +- .../history_view_reactions_animation.cpp | 71 ++- .../history_view_reactions_animation.h | 34 +- .../info/profile/info_profile_badge.cpp | 195 ++++++++ .../info/profile/info_profile_badge.h | 79 +++ .../info/profile/info_profile_cover.cpp | 348 +------------ .../info/profile/info_profile_cover.h | 86 +--- .../info_profile_emoji_status_panel.cpp | 322 ++++++++++++ .../profile/info_profile_emoji_status_panel.h | 67 +++ .../profile/info_profile_inner_widget.cpp | 6 +- .../info/profile/info_profile_values.cpp | 18 +- .../info/profile/info_profile_values.h | 4 +- .../settings/settings_information.cpp | 7 +- .../SourceFiles/settings/settings_main.cpp | 13 +- .../SourceFiles/settings/settings_premium.cpp | 20 +- .../SourceFiles/window/section_widget.cpp | 36 +- Telegram/SourceFiles/window/section_widget.h | 4 - .../SourceFiles/window/window_main_menu.cpp | 15 +- .../SourceFiles/window/window_main_menu.h | 4 +- Telegram/lib_ui | 2 +- 38 files changed, 941 insertions(+), 1055 deletions(-) create mode 100644 Telegram/SourceFiles/info/profile/info_profile_badge.cpp create mode 100644 Telegram/SourceFiles/info/profile/info_profile_badge.h create mode 100644 Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp create mode 100644 Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 14a5b6020..10a637518 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -807,8 +807,12 @@ PRIVATE info/polls/info_polls_results_widget.h info/profile/info_profile_actions.cpp info/profile/info_profile_actions.h + info/profile/info_profile_badge.cpp + info/profile/info_profile_badge.h info/profile/info_profile_cover.cpp info/profile/info_profile_cover.h + info/profile/info_profile_emoji_status_panel.cpp + info/profile/info_profile_emoji_status_panel.h info/profile/info_profile_icon.cpp info/profile/info_profile_icon.h info/profile/info_profile_inner_widget.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8f67b954a..8a8d725e7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1708,6 +1708,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_unlock_reactions" = "Unlock Premium Reactions"; "lng_premium_unlock_stickers" = "Unlock Premium Stickers"; "lng_premium_unlock_emoji" = "Unlock Animated Emoji"; +"lng_premium_unlock_status" = "Unlock Emoji Status"; "lng_premium_subscribe_months_12" = "Annual"; "lng_premium_subscribe_months_6" = "Semiannual"; @@ -1741,8 +1742,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_about_voice_to_text" = "Ability to read the transcript of any incoming voice message."; "lng_premium_summary_subtitle_no_ads" = "No Ads"; "lng_premium_summary_about_no_ads" = "No more ads in public channels where Telegram sometimes shows ads."; -"lng_premium_summary_subtitle_unique_reactions" = "Unique Reactions"; -"lng_premium_summary_about_unique_reactions" = "Additional animated reactions on messages available only to the Premium subscribers."; +"lng_premium_summary_subtitle_emoji_status" = "Emoji Status"; +"lng_premium_summary_about_emoji_status" = "Add any of thousands emoji next to your name to display current activity."; +"lng_premium_summary_subtitle_infinite_reactions" = "Infinite Reactions"; +"lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message."; "lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers"; "lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly."; "lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp index e13896c7f..14ddbe7bb 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_common.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/labels.h" +#include "ui/widgets/checkbox.h" #include "ui/widgets/input_fields.h" #include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 02c344d22..a7bc9d5fd 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_document.h" #include "data/data_session.h" -#include "data/data_message_reactions.h" #include "data/data_document_media.h" #include "data/data_streaming.h" #include "data/data_peer_values.h" @@ -63,7 +62,6 @@ using Data::ReactionId; struct Descriptor { PremiumPreview section = PremiumPreview::Stickers; DocumentData *requestedSticker = nullptr; - base::flat_map disabled; bool fromSettings = false; Fn hiddenCallback; Fn)> shownCallback; @@ -72,7 +70,6 @@ struct Descriptor { bool operator==(const Descriptor &a, const Descriptor &b) { return (a.section == b.section) && (a.requestedSticker == b.requestedSticker) - && (a.disabled == b.disabled) && (a.fromSettings == b.fromSettings); } @@ -120,8 +117,10 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_subtitle_voice_to_text(); case PremiumPreview::NoAds: return tr::lng_premium_summary_subtitle_no_ads(); - case PremiumPreview::Reactions: - return tr::lng_premium_summary_subtitle_unique_reactions(); + case PremiumPreview::EmojiStatus: + return tr::lng_premium_summary_subtitle_emoji_status(); + case PremiumPreview::InfiniteReactions: + return tr::lng_premium_summary_subtitle_infinite_reactions(); case PremiumPreview::Stickers: return tr::lng_premium_summary_subtitle_premium_stickers(); case PremiumPreview::AnimatedEmoji: @@ -146,8 +145,10 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_about_voice_to_text(); case PremiumPreview::NoAds: return tr::lng_premium_summary_about_no_ads(); - case PremiumPreview::Reactions: - return tr::lng_premium_summary_about_unique_reactions(); + case PremiumPreview::EmojiStatus: + return tr::lng_premium_summary_about_emoji_status(); + case PremiumPreview::InfiniteReactions: + return tr::lng_premium_summary_about_infinite_reactions(); case PremiumPreview::Stickers: return tr::lng_premium_summary_about_premium_stickers(); case PremiumPreview::AnimatedEmoji: @@ -465,6 +466,8 @@ struct VideoPreviewDocument { case PremiumPreview::AnimatedEmoji: return "animated_emoji"; case PremiumPreview::AdvancedChatManagement: return "advanced_chat_management"; + case PremiumPreview::EmojiStatus: return "emoji_status"; + case PremiumPreview::InfiniteReactions: return "infinite_reactions"; case PremiumPreview::ProfileBadge: return "profile_badge"; case PremiumPreview::AnimatedUserpics: return "animated_userpics"; } @@ -726,423 +729,12 @@ struct VideoPreviewDocument { return result; } - -class ReactionPreview final { -public: - ReactionPreview( - not_null controller, - const Data::Reaction &reaction, - ReactionDisableType type, - Fn update, - QPoint position); - - [[nodiscard]] bool playsEffect() const; - void paint(Painter &p); - void paintEffect(QPainter &p); - - void setOver(bool over); - void startAnimations(); - void cancelAnimations(); - [[nodiscard]] bool ready() const; - [[nodiscard]] bool disabled() const; - [[nodiscard]] QRect geometry() const; - -private: - void checkReady(); - - const not_null _controller; - const Fn _update; - const QPoint _position; - Ui::Animations::Simple _scale; - std::shared_ptr _centerMedia; - std::shared_ptr _aroundMedia; - std::unique_ptr _center; - std::unique_ptr _around; - std::unique_ptr _pathGradient; - QImage _cache1; - QImage _cache2; - bool _over = false; - bool _disabled = false; - bool _playRequested = false; - bool _aroundPlaying = false; - bool _centerPlaying = false; - rpl::lifetime _lifetime; - -}; - -ReactionPreview::ReactionPreview( - not_null controller, - const Data::Reaction &reaction, - ReactionDisableType type, - Fn update, - QPoint position) -: _controller(controller) -, _update(std::move(update)) -, _position(position) -, _centerMedia(reaction.centerIcon->createMediaView()) -, _aroundMedia(reaction.aroundAnimation->createMediaView()) -, _pathGradient( - HistoryView::MakePathShiftGradient( - controller->chatStyle(), - _update)) -, _disabled(type != ReactionDisableType::None) { - _centerMedia->checkStickerLarge(); - _aroundMedia->checkStickerLarge(); - checkReady(); - if (!_center || !_around) { - _controller->session().downloaderTaskFinished( - ) | rpl::take_while([=] { - checkReady(); - return !_center || !_around; - }) | rpl::start(_lifetime); - } -} - -QRect ReactionPreview::geometry() const { - const auto xsize = st::premiumReactionWidthSkip; - const auto ysize = st::premiumReactionHeightSkip; - return { _position - QPoint(xsize / 2, ysize / 2), QSize(xsize, ysize) }; -} - -void ReactionPreview::checkReady() { - const auto make = [&]( - const std::shared_ptr &media, - int size) { - const auto bytes = media->bytes(); - const auto filepath = media->owner()->filepath(); - auto result = ChatHelpers::LottiePlayerFromDocument( - media.get(), - nullptr, - ChatHelpers::StickerLottieSize::PremiumReactionPreview, - QSize(size, size) * style::DevicePixelRatio(), - Lottie::Quality::Default); - result->updates() | rpl::start_with_next(_update, _lifetime); - return result; - }; - if (!_center && _centerMedia->loaded()) { - _center = make(_centerMedia, st::premiumReactionSize); - } - if (!_around && _aroundMedia->loaded()) { - _around = make(_aroundMedia, st::premiumReactionAround); - } -} - -void ReactionPreview::setOver(bool over) { - if (_over == over || _disabled) { - return; - } - _over = over; - const auto from = st::premiumReactionScale; - _scale.start( - _update, - over ? from : 1., - over ? 1. : from, - st::slideWrapDuration); -} - -void ReactionPreview::startAnimations() { - if (_disabled) { - return; - } - _playRequested = true; - if (!_center || !_center->ready() || !_around || !_around->ready()) { - return; - } - _update(); -} - -void ReactionPreview::cancelAnimations() { - _playRequested = false; -} - -bool ReactionPreview::ready() const { - return _center && _center->ready(); -} - -bool ReactionPreview::disabled() const { - return _disabled; -} - -void ReactionPreview::paint(Painter &p) { - const auto center = st::premiumReactionSize; - const auto scale = _scale.value(_over ? 1. : st::premiumReactionScale); - const auto inner = QRect( - -center / 2, - -center / 2, - center, - center - ).translated(_position); - auto hq = PainterHighQualityEnabler(p); - const auto centerReady = _center && _center->ready(); - const auto staticCenter = centerReady && !_centerPlaying; - const auto use1 = staticCenter && scale == 1.; - const auto use2 = staticCenter && scale == st::premiumReactionScale; - const auto useScale = (!use1 && !use2 && scale != 1.); - if (useScale) { - p.save(); - p.translate(inner.center()); - p.scale(scale, scale); - p.translate(-inner.center()); - } - if (_disabled) { - p.setOpacity(kDisabledOpacity); - } - checkReady(); - if (centerReady) { - if (use1 || use2) { - auto &cache = use1 ? _cache1 : _cache2; - const auto use = int(std::round(center * scale)); - const auto rect = QRect(-use / 2, -use / 2, use, use).translated( - _position); - if (cache.isNull()) { - cache = _center->frame().scaledToWidth( - use * style::DevicePixelRatio(), - Qt::SmoothTransformation); - } - p.drawImage(rect, cache); - } else { - p.drawImage(inner, _center->frame()); - } - if (_centerPlaying) { - const auto almost = (_center->frameIndex() + 1) - == _center->framesCount(); - const auto marked = _center->markFrameShown(); - if (almost && marked) { - _centerPlaying = false; - } - } - if (_around - && _around->ready() - && !_aroundPlaying - && !_centerPlaying - && _playRequested) { - _aroundPlaying = _centerPlaying = true; - _playRequested = false; - } - } else { - p.setBrush(_controller->chatStyle()->msgServiceBg()); - ChatHelpers::PaintStickerThumbnailPath( - p, - _centerMedia.get(), - inner, - _pathGradient.get()); - } - if (useScale) { - p.restore(); - } else if (_disabled) { - p.setOpacity(1.); - } -} - -bool ReactionPreview::playsEffect() const { - return _aroundPlaying; -} - -void ReactionPreview::paintEffect(QPainter &p) { - if (!_aroundPlaying) { - return; - } - const auto size = st::premiumReactionAround; - const auto outer = QRect(-size/2, -size/2, size, size).translated( - _position); - const auto scale = _scale.value(_over ? 1. : st::premiumReactionScale); - auto hq = PainterHighQualityEnabler(p); - if (scale != 1.) { - p.save(); - p.translate(outer.center()); - p.scale(scale, scale); - p.translate(-outer.center()); - } - p.drawImage(outer, _around->frame()); - if (scale != 1.) { - p.restore(); - } - if (_aroundPlaying) { - const auto almost = (_around->frameIndex() + 1) - == _around->framesCount(); - const auto marked = _around->markFrameShown(); - if (almost && marked) { - _aroundPlaying = false; - } - } -} - -[[nodiscard]] not_null ReactionsPreview( - not_null parent, - not_null controller, - const base::flat_map &disabled, - Fn readyCallback) { - struct State { - std::vector> entries; - Ui::Text::String bottom; - int selected = -1; - bool readyInvoked = false; - }; - const auto result = Ui::CreateChild(parent.get()); - result->show(); - - auto &lifetime = result->lifetime(); - const auto state = lifetime.make_state(); - - result->setMouseTracking(true); - - parent->sizeValue( - ) | rpl::start_with_next([=] { - result->setGeometry(parent->rect()); - }, result->lifetime()); - - using namespace HistoryView; - const auto list = controller->session().data().reactions().list( - Data::Reactions::Type::Active); - const auto count = ranges::count(list, true, &Data::Reaction::premium); - const auto rows = (count + kReactionsPerRow - 1) / kReactionsPerRow; - const auto inrowmax = rows ? ((count + rows - 1) / rows) : 1; - const auto inrowless = (inrowmax * rows - count); - const auto inrowmore = rows - inrowless; - const auto inmaxrows = inrowmore * inrowmax; - auto index = 0; - auto disableType = ReactionDisableType::None; - for (const auto &reaction : list) { - if (!reaction.premium) { - continue; - } - const auto inrow = (index < inmaxrows) ? inrowmax : (inrowmax - 1); - const auto row = (index < inmaxrows) - ? (index / inrow) - : (inrowmore + ((index - inmaxrows) / inrow)); - const auto column = (index < inmaxrows) - ? (index % inrow) - : ((index - inmaxrows) % inrow); - ++index; - if (!reaction.centerIcon || !reaction.aroundAnimation) { - continue; - } - const auto i = disabled.find(reaction.id); - const auto disable = (i != end(disabled)) - ? i->second - : ReactionDisableType::None; - if (disable != ReactionDisableType::None) { - disableType = disable; - } - state->entries.push_back(std::make_unique( - controller, - reaction, - disable, - [=] { result->update(); }, - QPoint(ComputeX(column, inrow), ComputeY(row, rows)))); - } - - const auto bottom1 = tr::lng_reaction_premium_info(tr::now); - const auto bottom2 = (disableType == ReactionDisableType::None) - ? QString() - : (disableType == ReactionDisableType::Group) - ? tr::lng_reaction_premium_no_group(tr::now) - : tr::lng_reaction_premium_no_channel(tr::now); - state->bottom.setText( - st::defaultTextStyle, - (bottom1 + '\n' + bottom2).trimmed()); - - result->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(result); - auto effects = std::vector>(); - auto ready = 0; - for (const auto &entry : state->entries) { - entry->paint(p); - if (entry->ready()) { - ++ready; - } - if (entry->playsEffect()) { - effects.push_back([&] { - entry->paintEffect(p); - }); - } - } - if (!state->readyInvoked - && readyCallback - && ready > 0 - && ready == state->entries.size()) { - state->readyInvoked = true; - readyCallback(); - - } - const auto padding = st::boxRowPadding; - const auto available = parent->width() - - padding.left() - - padding.right(); - const auto top = st::premiumReactionInfoTop - + ((state->bottom.maxWidth() > available) - ? st::normalFont->height - : 0); - p.setPen(st::premiumButtonFg); - state->bottom.draw( - p, - padding.left(), - top, - available, - style::al_top); - for (const auto &paint : effects) { - paint(); - } - }, lifetime); - - const auto lookup = [=](QPoint point) { - auto index = 0; - for (const auto &entry : state->entries) { - if (entry->geometry().contains(point) && !entry->disabled()) { - return index; - } - ++index; - } - return -1; - }; - const auto select = [=](int index) { - const auto wasInside = (state->selected >= 0); - const auto nowInside = (index >= 0); - if (state->selected != index) { - if (wasInside) { - state->entries[state->selected]->setOver(false); - } - if (nowInside) { - state->entries[index]->setOver(true); - } - state->selected = index; - } - if (wasInside != nowInside) { - result->setCursor(nowInside - ? style::cur_pointer - : style::cur_default); - } - }; - result->events( - ) | rpl::start_with_next([=](not_null event) { - if (event->type() == QEvent::MouseButtonPress) { - const auto point = static_cast(event.get())->pos(); - if (state->selected >= 0) { - state->entries[state->selected]->cancelAnimations(); - } - if (const auto index = lookup(point); index >= 0) { - state->entries[index]->startAnimations(); - } - } else if (event->type() == QEvent::MouseMove) { - const auto point = static_cast(event.get())->pos(); - select(lookup(point)); - } else if (event->type() == QEvent::Leave) { - select(-1); - } - }, lifetime); - - return result; -} - [[nodiscard]] not_null GenerateDefaultPreview( not_null parent, not_null controller, PremiumPreview section, Fn readyCallback) { switch (section) { - case PremiumPreview::Reactions: - return ReactionsPreview(parent, controller, {}, readyCallback); case PremiumPreview::Stickers: return StickersPreview(parent, controller, readyCallback); default: @@ -1228,8 +820,6 @@ void PreviewBox( Ui::RpWidget *content = nullptr; Ui::RpWidget *stickersPreload = nullptr; bool stickersPreloadReady = false; - Ui::RpWidget *reactionsPreload = nullptr; - bool reactionsPreloadReady = false; bool preloadScheduled = false; bool showFinished = false; Ui::Animations::Simple animation; @@ -1292,21 +882,6 @@ void PreviewBox( ready); state->stickersPreload->hide(); } - if (now != PremiumPreview::Reactions && !state->reactionsPreload) { - const auto ready = [=] { - if (state->reactionsPreload) { - state->reactionsPreloadReady = true; - } else { - state->preload(); - } - }; - state->reactionsPreload = GenerateDefaultPreview( - outer, - controller, - PremiumPreview::Reactions, - ready); - state->reactionsPreload->hide(); - } }; switch (descriptor.section) { @@ -1315,13 +890,6 @@ void PreviewBox( ? StickerPreview(outer, controller, media, state->preload) : StickersPreview(outer, controller, state->preload); break; - case PremiumPreview::Reactions: - state->content = ReactionsPreview( - outer, - controller, - descriptor.disabled, - state->preload); - break; default: state->content = GenericPreview( outer, @@ -1381,13 +949,6 @@ void PreviewBox( if (base::take(state->stickersPreloadReady)) { state->preload(); } - } else if (now == PremiumPreview::Reactions - && state->reactionsPreload) { - state->content = base::take(state->reactionsPreload); - state->content->show(); - if (base::take(state->reactionsPreloadReady)) { - state->preload(); - } } else { state->content = GenerateDefaultPreview( outer, @@ -1457,12 +1018,14 @@ void PreviewBox( }; auto unlock = state->selected.value( ) | rpl::map([=](PremiumPreview section) { - return (section == PremiumPreview::Reactions) + return (section == PremiumPreview::InfiniteReactions) ? tr::lng_premium_unlock_reactions() : (section == PremiumPreview::Stickers) ? tr::lng_premium_unlock_stickers() : (section == PremiumPreview::AnimatedEmoji) ? tr::lng_premium_unlock_emoji() + : (section == PremiumPreview::EmojiStatus) + ? tr::lng_premium_unlock_status() : tr::lng_premium_more_about(); }) | rpl::flatten_latest(); auto button = descriptor.fromSettings @@ -1626,17 +1189,8 @@ void ShowPremiumPreviewBox( not_null controller, PremiumPreview section, Fn)> shown) { - ShowPremiumPreviewBox(controller, section, {}, std::move(shown)); -} - -void ShowPremiumPreviewBox( - not_null controller, - PremiumPreview section, - const base::flat_map &disabled, - Fn)> shown) { Show(controller, Descriptor{ .section = section, - .disabled = disabled, .shownCallback = std::move(shown), }); } diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index ec74b091a..bca679670 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -42,7 +42,8 @@ enum class PremiumPreview { FasterDownload, VoiceToText, NoAds, - Reactions, + EmojiStatus, + InfiniteReactions, Stickers, AnimatedEmoji, AdvancedChatManagement, @@ -51,23 +52,12 @@ enum class PremiumPreview { kCount, }; -enum class ReactionDisableType { - None, - Group, - Channel, -}; void ShowPremiumPreviewBox( not_null controller, PremiumPreview section, Fn)> shown = nullptr); -void ShowPremiumPreviewBox( - not_null controller, - PremiumPreview section, - const base::flat_map &disabled, - Fn)> shown = nullptr); - void ShowPremiumPreviewToBuy( not_null controller, PremiumPreview section, diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index fe17c0cff..1cdbab6b7 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -521,7 +521,9 @@ void ReactionsSettingsBox( button->setClickedCallback([=, id = r.id] { if (premium && !controller->session().premium()) { - ShowPremiumPreviewBox(controller, PremiumPreview::Reactions); + ShowPremiumPreviewBox( + controller, + PremiumPreview::InfiniteReactions); return; } checkButton(button); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 94e686e2c..e99c67bb5 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -687,8 +687,7 @@ void EmojiListWidget::setSingleSize(QSize size) { _innerPosition = QPoint( (area.width() - esize) / 2, (area.height() - esize) / 2); - const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize); - const auto customSkip = (esize - customSize) / 2; + const auto customSkip = (esize - _customSingleSize) / 2; _customPosition = QPoint(customSkip, customSkip); _picker->setSingleSize(_singleSize); } @@ -964,7 +963,9 @@ void EmojiListWidget::paint( drawCollapsedBadge(p, w - _areaPosition, info.count); continue; } - if (selected && st().overBg->c.alpha() > 0) { + if (!_grabbingChosen + && selected + && st().overBg->c.alpha() > 0) { auto tl = w; if (rtl()) { tl.setX(width() - tl.x() - st::emojiPanArea.width()); @@ -1128,12 +1129,17 @@ EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const { EmojiChosen EmojiListWidget::lookupChosen( EmojiPtr emoji, not_null over) { + const auto rect = emojiRect(over->section, over->index); + const auto icon = QRect( + rect.x() + (_singleSize.width() - st::stickersPremium.width()) / 2, + rect.y() + (_singleSize.height() - st::stickersPremium.height()) / 2, + rect.width(), + rect.height()); return { .emoji = emoji, .messageSendingFrom = { .type = Ui::MessageSendingAnimationFrom::Type::Emoji, - .globalStartGeometry = mapToGlobal( - emojiRect(over->section, over->index)), + .globalStartGeometry = mapToGlobal(icon), }, }; } @@ -1144,14 +1150,19 @@ FileChosen EmojiListWidget::lookupChosen( Api::SendOptions options) { _grabbingChosen = true; const auto guard = gsl::finally([&] { _grabbingChosen = false; }); - const auto rect = emojiRect(over->section, over->index); + const auto rect = over ? emojiRect(over->section, over->index) : QRect(); + const auto emoji = over ? QRect( + rect.topLeft() + _areaPosition + _innerPosition + _customPosition, + QSize(_customSingleSize, _customSingleSize) + ) : QRect(); + return { .document = custom, .options = options, .messageSendingFrom = { .type = Ui::MessageSendingAnimationFrom::Type::Emoji, - .globalStartGeometry = over ? mapToGlobal(rect) : QRect(), - .frame = over ? Ui::GrabWidgetToImage(this, rect) : QImage(), + .globalStartGeometry = over ? mapToGlobal(emoji) : QRect(), + .frame = over ? Ui::GrabWidgetToImage(this, emoji) : QImage(), }, }; } @@ -1246,7 +1257,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { break; case Mode::FullReactions: case Mode::RecentReactions: - Settings::ShowPremium(_controller, u"unique_reactions"_q); + Settings::ShowPremium(_controller, u"infinite_reactions"_q); break; case Mode::EmojiStatus: Settings::ShowPremium(_controller, u"emoji_status"_q); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index df0a471c6..a4a973c96 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -25,15 +25,17 @@ struct CustomEmojiId { DocumentId id = 0; }; +enum class CustomEmojiSizeTag : uchar { + Normal, + Large, + Isolated, + + kCount, +}; + class CustomEmojiManager final : public base::has_weak_ptr { public: - enum class SizeTag : uchar { - Normal, - Large, - Isolated, - - kCount, - }; + using SizeTag = CustomEmojiSizeTag; CustomEmojiManager(not_null owner); ~CustomEmojiManager(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 59d0cd095..2f28d2afd 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" +#include "history/view/reactions/history_view_reactions_animation.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/history_view_message.h" @@ -49,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/delete_messages_box.h" #include "boxes/report_messages_box.h" #include "boxes/sticker_set_box.h" +#include "boxes/premium_preview_box.h" #include "chat_helpers/message_field.h" #include "chat_helpers/emoji_interactions.h" #include "history/history_widget.h" @@ -401,7 +403,9 @@ HistoryInner::HistoryInner( _reactionsManager->premiumPromoChosen( ) | rpl::start_with_next([=](FullMsgId context) { _reactionsManager->updateButton({}); - premiumPromoChosen(context); + ShowPremiumPreviewBox( + _controller, + PremiumPreview::InfiniteReactions); }, lifetime()); session().data().itemRemoved( @@ -492,12 +496,6 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) { } } -void HistoryInner::premiumPromoChosen(FullMsgId context) { - if (const auto item = session().data().message(context)) { - ShowPremiumPromoBox(_controller, item); - } -} - Main::Session &HistoryInner::session() const { return _controller->session(); } @@ -2456,7 +2454,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { desiredPosition, reactItem, [=](ChosenReaction reaction) { reactionChosen(reaction); }, - [=](FullMsgId context) { premiumPromoChosen(context); }, + [=](FullMsgId context) { ShowPremiumPreviewBox( + controller, + PremiumPreview::InfiniteReactions); }, _controller->cachedReactionIconFactory().createMethod()) : AttachSelectorResult::Skipped; if (attached == AttachSelectorResult::Failed) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 11b4a0114..7524709ae 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -399,7 +399,6 @@ private: -> HistoryView::Reactions::ButtonParameters; void toggleFavoriteReaction(not_null view) const; void reactionChosen(const ChosenReaction &reaction); - void premiumPromoChosen(FullMsgId context); void setupSharingDisallowed(); [[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const; diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index c8b4016d1..3fc0c047c 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -29,14 +29,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { -ReactionAnimationArgs ReactionAnimationArgs::translated( - QPoint point) const { - return { - .id = id, - .flyIcon = flyIcon, - .flyFrom = flyFrom.translated(point), - }; -} +struct BottomInfo::Reaction { + mutable std::unique_ptr animation; + mutable QImage image; + ReactionId id; + QString countText; + int count = 0; + int countTextWidth = 0; + bool chosen = false; +}; BottomInfo::BottomInfo( not_null<::Data::Reactions*> reactionsOwner, @@ -389,13 +390,19 @@ void BottomInfo::paintReactions( } if (!animations.empty()) { const auto now = context.now; - context.reactionInfo->effectPaint = [=](QPainter &p) { + context.reactionInfo->effectPaint = [ + now, + origin, + list = std::move(animations) + ](QPainter &p) { auto result = QRect(); - for (const auto &single : animations) { + for (const auto &single : list) { const auto area = single.animation->paintGetArea( p, origin, single.target, + QColor(255, 255, 255, 0), // Colored, for emoji status. + QRect(), // Clip, for emoji status. now); result = result.isEmpty() ? area : result.united(area); } @@ -553,7 +560,7 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) { } void BottomInfo::animateReaction( - ReactionAnimationArgs &&args, + Reactions::AnimationArgs &&args, Fn repaint) { const auto i = ranges::find(_reactions, args.id, &Reaction::id); if (i == end(_reactions)) { diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index 0818764c0..def9ad338 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "history/view/history_view_object.h" -#include "data/data_message_reaction_id.h" #include "ui/text/text.h" #include "base/flags.h" @@ -19,21 +18,16 @@ class AnimatedIcon; namespace Data { class Reactions; +struct ReactionId; +struct MessageReaction; } // namespace Data namespace HistoryView { namespace Reactions { class Animation; +struct AnimationArgs; } // namespace Reactions -struct ReactionAnimationArgs { - ::Data::ReactionId id; - QImage flyIcon; - QRect flyFrom; - - [[nodiscard]] ReactionAnimationArgs translated(QPoint point) const; -}; - using PaintContext = Ui::ChatPaintContext; class Message; @@ -86,7 +80,7 @@ public: const PaintContext &context) const; void animateReaction( - ReactionAnimationArgs &&args, + Reactions::AnimationArgs &&args, Fn repaint); [[nodiscard]] auto takeReactionAnimations() -> base::flat_map>; @@ -95,15 +89,7 @@ public: std::unique_ptr> animations); private: - struct Reaction { - mutable std::unique_ptr animation; - mutable QImage image; - ReactionId id; - QString countText; - int count = 0; - int countTextWidth = 0; - bool chosen = false; - }; + struct Reaction; void layout(); void layoutDateText(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 4f855dd2c..1eb506f26 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1116,7 +1116,7 @@ void Element::clickHandlerPressedChanged( } } -void Element::animateReaction(ReactionAnimationArgs &&args) { +void Element::animateReaction(Reactions::AnimationArgs &&args) { } void Element::animateUnreadReactions() { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3f0655d4c..31465fc59 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -34,24 +34,23 @@ struct ChatPaintContext; class ChatStyle; } // namespace Ui +namespace HistoryView::Reactions { +struct ButtonParameters; +struct AnimationArgs; +class Animation; +class InlineList; +} // namespace HistoryView::Reactions + namespace HistoryView { +using PaintContext = Ui::ChatPaintContext; enum class PointState : char; enum class InfoDisplayType : char; -struct ReactionAnimationArgs; struct StateRequest; struct TextState; class Media; //struct ExternalLottieInfo; -using PaintContext = Ui::ChatPaintContext; - -namespace Reactions { -struct ButtonParameters; -class Animation; -class InlineList; -} // namespace Reactions - enum class Context : char { History, Replies, @@ -433,7 +432,7 @@ public: [[nodiscard]] bool markSponsoredViewed(int shownFromTop) const; - virtual void animateReaction(ReactionAnimationArgs &&args); + virtual void animateReaction(Reactions::AnimationArgs &&args); void animateUnreadReactions(); [[nodiscard]] virtual auto takeReactionAnimations() -> base::flat_map< diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 7f68a446f..d6d9e7dab 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_text.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" +#include "history/view/reactions/history_view_reactions_animation.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_context_menu.h" #include "history/view/history_view_element.h" @@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style.h" #include "lang/lang_keys.h" #include "boxes/delete_messages_box.h" +#include "boxes/premium_preview_box.h" #include "boxes/peers/edit_participant_box.h" #include "data/data_session.h" #include "data/data_folder.h" @@ -390,9 +392,9 @@ ListWidget::ListWidget( _reactionsManager->premiumPromoChosen( ) | rpl::start_with_next([=] { _reactionsManager->updateButton({}); - if (const auto item = _reactionsItem.current()) { - ShowPremiumPromoBox(_controller, item); - } + ShowPremiumPreviewBox( + _controller, + PremiumPreview::InfiniteReactions); }, lifetime()); Reactions::SetupManagerList( diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index e746edb68..168074ffd 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -345,7 +345,7 @@ void Message::applyGroupAdminChanges( } } -void Message::animateReaction(ReactionAnimationArgs &&args) { +void Message::animateReaction(Reactions::AnimationArgs &&args) { const auto item = message(); const auto media = this->media(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 039427966..119a549ec 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -138,7 +138,7 @@ public: void applyGroupAdminChanges( const base::flat_set &changes) override; - void animateReaction(ReactionAnimationArgs &&args) override; + void animateReaction(Reactions::AnimationArgs &&args) override; auto takeReactionAnimations() -> base::flat_map< Data::ReactionId, diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index c62fbae52..efe35af6d 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -453,13 +453,18 @@ void InlineList::paint( } if (!animations.empty()) { const auto now = context.now; - context.reactionInfo->effectPaint = [=](QPainter &p) { + context.reactionInfo->effectPaint = [ + now, + list = std::move(animations) + ](QPainter &p) { auto result = QRect(); - for (const auto &single : animations) { + for (const auto &single : list) { const auto area = single.animation->paintGetArea( p, QPoint(), single.target, + QColor(255, 255, 255, 0), // Colored, for emoji status. + QRect(), // Clip, for emoji status. now); result = result.isEmpty() ? area : result.united(area); } @@ -494,7 +499,7 @@ bool InlineList::getState( } void InlineList::animate( - ReactionAnimationArgs &&args, + AnimationArgs &&args, Fn repaint) { const auto i = ranges::find(_buttons, args.id, &Button::id); if (i == end(_buttons)) { diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h index 3cd64ff9b..e97e0198f 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.h @@ -24,13 +24,13 @@ using PaintContext = Ui::ChatPaintContext; class Message; struct TextState; struct UserpicInRow; -struct ReactionAnimationArgs; } // namespace HistoryView namespace HistoryView::Reactions { using ::Data::ReactionId; using ::Data::MessageReaction; +struct AnimationArgs; class Animation; struct InlineListData { @@ -79,7 +79,7 @@ public: not_null outResult) const; void animate( - ReactionAnimationArgs &&args, + AnimationArgs &&args, Fn repaint); [[nodiscard]] auto takeAnimations() -> base::flat_map>; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp index ac11ea24e..8ef575423 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.cpp @@ -32,6 +32,14 @@ constexpr auto kMiniCopiesMaxScaleMax = 0.9; } // namespace +AnimationArgs AnimationArgs::translated(QPoint point) const { + return { + .id = id, + .flyIcon = flyIcon, + .flyFrom = flyFrom.translated(point), + }; +} + auto Animation::flyCallback() { return [=] { if (!_fly.animating()) { @@ -54,22 +62,28 @@ auto Animation::callback() { Animation::Animation( not_null<::Data::Reactions*> owner, - ReactionAnimationArgs &&args, + AnimationArgs &&args, Fn repaint, - int size) + int size, + Data::CustomEmojiSizeTag customSizeTag) : _owner(owner) , _repaint(std::move(repaint)) , _flyFrom(args.flyFrom) { const auto &list = owner->list(::Data::Reactions::Type::All); auto centerIcon = (DocumentData*)nullptr; - auto centerIconSize = size; auto aroundAnimation = (DocumentData*)nullptr; if (const auto customId = args.id.custom()) { - centerIconSize = Ui::Text::AdjustCustomEmojiSize(st::emojiSize); + const auto esize = Data::FrameSizeFromTag(customSizeTag) + / style::DevicePixelRatio(); const auto data = &owner->owner(); const auto document = data->document(customId); - _custom = data->customEmojiManager().create(document, callback()); - _customSize = centerIconSize; + _custom = data->customEmojiManager().create( + document, + callback(), + customSizeTag); + _colored = std::make_unique(); + _customSize = esize; + _centerSizeMultiplier = _customSize / float64(size); aroundAnimation = owner->chooseGenericAnimation(document); } else { const auto i = ranges::find(list, args.id, &::Data::Reaction::id); @@ -78,6 +92,7 @@ Animation::Animation( } centerIcon = i->centerIcon; aroundAnimation = i->aroundAnimation; + _centerSizeMultiplier = 1.; } const auto resolve = [&]( std::unique_ptr &icon, @@ -96,7 +111,7 @@ Animation::Animation( }); return true; }; - if (!_custom && !resolve(_center, centerIcon, centerIconSize)) { + if (!_custom && !resolve(_center, centerIcon, size)) { return; } resolve(_effect, aroundAnimation, size * 2); @@ -109,7 +124,6 @@ Animation::Animation( } else { startAnimations(); } - _centerSizeMultiplier = centerIconSize / float64(size); _valid = true; } @@ -119,21 +133,26 @@ QRect Animation::paintGetArea( QPainter &p, QPoint origin, QRect target, + const QColor &colored, + QRect clip, crl::time now) const { if (_flyIcon.isNull()) { - paintCenterFrame(p, target, now); const auto wide = QRect( target.topLeft() - QPoint(target.width(), target.height()) / 2, target.size() * 2); - if (const auto effect = _effect.get()) { - p.drawImage(wide, effect->frame()); - } - paintMiniCopies(p, target.center(), now); - return _miniCopies.empty() + const auto area = _miniCopies.empty() ? wide : QRect( target.topLeft() - QPoint(target.width(), target.height()), target.size() * 3); + if (clip.isEmpty() || area.intersects(clip)) { + paintCenterFrame(p, target, colored, now); + if (const auto effect = _effect.get()) { + p.drawImage(wide, effect->frame()); + } + paintMiniCopies(p, target.center(), colored, now); + } + return area; } const auto from = _flyFrom.translated(origin); const auto lshift = target.width() / 4; @@ -152,21 +171,24 @@ QRect Animation::paintGetArea( anim::interpolate(from.width(), target.width(), progress), anim::interpolate(from.height(), target.height(), progress)); const auto wide = rect.marginsAdded(margins); - if (progress < 1.) { - p.setOpacity(1. - progress); - p.drawImage(rect, _flyIcon); + if (clip.isEmpty() || wide.intersects(clip)) { + if (progress < 1.) { + p.setOpacity(1. - progress); + p.drawImage(rect, _flyIcon); + } + if (progress > 0.) { + p.setOpacity(progress); + paintCenterFrame(p, wide, colored, now); + } + p.setOpacity(1.); } - if (progress > 0.) { - p.setOpacity(progress); - paintCenterFrame(p, wide, now); - } - p.setOpacity(1.); return wide; } void Animation::paintCenterFrame( QPainter &p, QRect target, + const QColor &colored, crl::time now) const { Expects(_center || _custom); @@ -182,8 +204,10 @@ void Animation::paintCenterFrame( p.drawImage(rect, _center->frame()); } else { const auto scaled = (size.width() != _customSize); + _colored->color = colored; _custom->paint(p, { .preview = QColor(0, 0, 0, 0), + .colored = _colored.get(), .size = { _customSize, _customSize }, .now = now, .scale = (scaled ? (size.width() / float64(_customSize)) : 1.), @@ -198,6 +222,7 @@ void Animation::paintCenterFrame( void Animation::paintMiniCopies( QPainter &p, QPoint center, + const QColor &colored, crl::time now) const { Expects(_miniCopies.empty() || _custom != nullptr); @@ -213,8 +238,10 @@ void Animation::paintMiniCopies( / float64(kMiniCopiesDurationMax); const auto scaleOut = kMiniCopiesScaleOutDuration / float64(kMiniCopiesDurationMax); + _colored->color = colored; auto context = Ui::Text::CustomEmoji::Context{ .preview = preview, + .colored = _colored.get(), .size = size, .now = now, .scaled = true, diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h index 4341be001..17ac9c3b1 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_animation.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/effects/animations.h" +#include "data/data_message_reaction_id.h" namespace Ui { class AnimatedIcon; @@ -19,21 +20,27 @@ class CustomEmoji; namespace Data { class Reactions; +enum class CustomEmojiSizeTag : uchar; } // namespace Data -namespace HistoryView { -struct ReactionAnimationArgs; -} // namespace HistoryView - namespace HistoryView::Reactions { +struct AnimationArgs { + ::Data::ReactionId id; + QImage flyIcon; + QRect flyFrom; + + [[nodiscard]] AnimationArgs translated(QPoint point) const; +}; + class Animation final { public: Animation( not_null<::Data::Reactions*> owner, - ReactionAnimationArgs &&args, + AnimationArgs &&args, Fn repaint, - int size); + int size, + Data::CustomEmojiSizeTag customSizeTag = {}); ~Animation(); void setRepaintCallback(Fn repaint); @@ -41,6 +48,8 @@ public: QPainter &p, QPoint origin, QRect target, + const QColor &colored, + QRect clip, crl::time now) const; [[nodiscard]] bool flying() const; @@ -71,14 +80,23 @@ private: int to, int top, float64 progress) const; - void paintCenterFrame(QPainter &p, QRect target, crl::time now) const; - void paintMiniCopies(QPainter &p, QPoint center, crl::time now) const; + void paintCenterFrame( + QPainter &p, + QRect target, + const QColor &colored, + crl::time now) const; + void paintMiniCopies( + QPainter &p, + QPoint center, + const QColor &colored, + crl::time now) const; void generateMiniCopies(int size); const not_null<::Data::Reactions*> _owner; Fn _repaint; QImage _flyIcon; std::unique_ptr _custom; + std::unique_ptr _colored; std::unique_ptr _center; std::unique_ptr _effect; std::vector _miniCopies; diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp new file mode 100644 index 000000000..c00f19a28 --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp @@ -0,0 +1,195 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "info/profile/info_profile_badge.h" + +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/stickers/data_custom_emoji.h" +#include "info/profile/info_profile_values.h" +#include "info/profile/info_profile_emoji_status_panel.h" +#include "lang/lang_keys.h" +#include "ui/widgets/buttons.h" +#include "main/main_session.h" +#include "styles/style_info.h" + +namespace Info::Profile { +namespace { + +} // namespace + +Badge::Badge( + not_null parent, + const style::InfoPeerBadge &st, + not_null peer, + EmojiStatusPanel *emojiStatusPanel, + Fn animationPaused, + int customStatusLoopsLimit, + base::flags allowed) +: _parent(parent) +, _st(st) +, _peer(peer) +, _emojiStatusPanel(emojiStatusPanel) +, _customStatusLoopsLimit(customStatusLoopsLimit) +, _allowed(allowed) +, _animationPaused(std::move(animationPaused)) { + rpl::combine( + BadgeValue(peer), + EmojiStatusIdValue(peer) + ) | rpl::start_with_next([=](BadgeType badge, DocumentId emojiStatusId) { + setBadge(badge, emojiStatusId); + }, _lifetime); +} + +Ui::RpWidget *Badge::widget() const { + return _view.data(); +} + +void Badge::setBadge(BadgeType badge, DocumentId emojiStatusId) { + if (!(_allowed & badge) + || (!_peer->session().premiumBadgesShown() + && badge == BadgeType::Premium)) { + badge = BadgeType::None; + } + if (!(_allowed & badge)) { + badge = BadgeType::None; + } + if (badge != BadgeType::Premium) { + emojiStatusId = 0; + } + if (_badge == badge && _emojiStatusId == emojiStatusId) { + return; + } + _badge = badge; + _emojiStatusId = emojiStatusId; + _emojiStatus = nullptr; + _emojiStatusColored = nullptr; + _view.destroy(); + if (_badge == BadgeType::None) { + _updated.fire({}); + return; + } + _view.create(_parent); + _view->show(); + switch (_badge) { + case BadgeType::Verified: + case BadgeType::Premium: { + if (_emojiStatusId) { + _emojiStatus = _peer->owner().customEmojiManager().create( + _emojiStatusId, + [raw = _view.data()] { raw->update(); }, + sizeTag()); + if (_customStatusLoopsLimit > 0) { + _emojiStatus = std::make_unique( + std::move(_emojiStatus), + _customStatusLoopsLimit); + } + _emojiStatusColored = std::make_unique< + Ui::Text::CustomEmojiColored + >(); + const auto emoji = Data::FrameSizeFromTag(sizeTag()) + / style::DevicePixelRatio(); + _view->resize(emoji, emoji); + _view->paintRequest( + ) | rpl::start_with_next([=, check = _view.data()]{ + _emojiStatusColored->color = _st.premiumFg->c; + auto args = Ui::Text::CustomEmoji::Context{ + .preview = st::windowBgOver->c, + .colored = _emojiStatusColored.get(), + .now = crl::now(), + .paused = _animationPaused && _animationPaused(), + }; + if (!_emojiStatusPanel + || !_emojiStatusPanel->paintBadgeFrame(check)) { + Painter p(check); + _emojiStatus->paint(p, args); + } + }, _view->lifetime()); + } else { + const auto icon = (_badge == BadgeType::Verified) + ? &_st.verified + : &_st.premium; + _view->resize(icon->size()); + _view->paintRequest( + ) | rpl::start_with_next([=, check = _view.data()]{ + Painter p(check); + icon->paint(p, 0, 0, check->width()); + }, _view->lifetime()); + } + } break; + case BadgeType::Scam: + case BadgeType::Fake: { + const auto fake = (_badge == BadgeType::Fake); + const auto size = Ui::ScamBadgeSize(fake); + const auto skip = st::infoVerifiedCheckPosition.x(); + _view->resize( + size.width() + 2 * skip, + size.height() + 2 * skip); + _view->paintRequest( + ) | rpl::start_with_next([=, badge = _view.data()]{ + Painter p(badge); + Ui::DrawScamBadge( + fake, + p, + badge->rect().marginsRemoved({ skip, skip, skip, skip }), + badge->width(), + st::attentionButtonFg); + }, _view->lifetime()); + } break; + } + + if (_badge != BadgeType::Premium || !_premiumClickCallback) { + _view->setAttribute(Qt::WA_TransparentForMouseEvents); + } else { + _view->setClickedCallback(_premiumClickCallback); + } + + _updated.fire({}); +} + +void Badge::setPremiumClickCallback(Fn callback) { + _premiumClickCallback = std::move(callback); + if (_view && _badge == BadgeType::Premium) { + if (!_premiumClickCallback) { + _view->setAttribute(Qt::WA_TransparentForMouseEvents); + } else { + _view->setAttribute(Qt::WA_TransparentForMouseEvents, false); + _view->setClickedCallback(_premiumClickCallback); + } + } +} + +rpl::producer<> Badge::updated() const { + return _updated.events(); +} + +void Badge::move(int left, int top, int bottom) { + if (!_view) { + return; + } + const auto star = !_emojiStatus + && (_badge == BadgeType::Premium || _badge == BadgeType::Verified); + const auto fake = !_emojiStatus && !star; + const auto skip = fake ? 0 : _st.position.x(); + const auto badgeLeft = left + skip; + const auto badgeTop = top + + (star + ? _st.position.y() + : (bottom - top - _view->height()) / 2); + _view->moveToLeft(badgeLeft, badgeTop); +} + +Data::CustomEmojiSizeTag Badge::sizeTag() const { + using SizeTag = Data::CustomEmojiSizeTag; + return (_st.sizeTag == 2) + ? SizeTag::Isolated + : (_st.sizeTag == 1) + ? SizeTag::Large + : SizeTag::Normal; +} + +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.h b/Telegram/SourceFiles/info/profile/info_profile_badge.h new file mode 100644 index 000000000..4b3fbbd8d --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.h @@ -0,0 +1,79 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/flags.h" +#include "base/object_ptr.h" + +namespace style { +struct InfoPeerBadge; +} // namespace style + +namespace Data { +enum class CustomEmojiSizeTag : uchar; +} // namespace Data + +namespace Ui { +class RpWidget; +class AbstractButton; +} // namespace Ui + +namespace Info::Profile { + +class EmojiStatusPanel; + +enum class BadgeType { + None = 0x00, + Verified = 0x01, + Premium = 0x02, + Scam = 0x04, + Fake = 0x08, +}; +inline constexpr bool is_flag_type(BadgeType) { return true; } + +class Badge final { +public: + Badge( + not_null parent, + const style::InfoPeerBadge &st, + not_null peer, + EmojiStatusPanel *emojiStatusPanel, + Fn animationPaused, + int customStatusLoopsLimit = 0, + base::flags allowed = base::flags::from_raw(-1)); + + [[nodiscard]] Ui::RpWidget *widget() const; + + void setPremiumClickCallback(Fn callback); + [[nodiscard]] rpl::producer<> updated() const; + void move(int left, int top, int bottom); + + [[nodiscard]] Data::CustomEmojiSizeTag sizeTag() const; + +private: + void setBadge(BadgeType badge, DocumentId emojiStatusId); + + const not_null _parent; + const style::InfoPeerBadge &_st; + const not_null _peer; + EmojiStatusPanel *_emojiStatusPanel = nullptr; + const int _customStatusLoopsLimit = 0; + DocumentId _emojiStatusId = 0; + std::unique_ptr _emojiStatus; + std::unique_ptr _emojiStatusColored; + base::flags _allowed; + BadgeType _badge = BadgeType(); + Fn _premiumClickCallback; + Fn _animationPaused; + object_ptr _view = { nullptr }; + rpl::event_stream<> _updated; + rpl::lifetime _lifetime; + +}; + +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 3d3a74aba..ae404f063 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -7,48 +7,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_cover.h" -#include "data/data_photo.h" #include "data/data_peer_values.h" #include "data/data_channel.h" #include "data/data_chat.h" -#include "data/data_user.h" +#include "data/data_peer.h" #include "data/data_changes.h" -#include "data/data_session.h" -#include "data/data_document.h" -#include "data/data_emoji_statuses.h" -#include "data/stickers/data_custom_emoji.h" -#include "editor/photo_editor_layer_widget.h" #include "info/profile/info_profile_values.h" +#include "info/profile/info_profile_badge.h" +#include "info/profile/info_profile_emoji_status_panel.h" #include "info/info_controller.h" -#include "info/info_memento.h" #include "lang/lang_keys.h" -#include "menu/menu_send.h" // SendMenu::Type. -#include "ui/boxes/confirm_box.h" -#include "ui/boxes/time_picker_box.h" #include "ui/widgets/labels.h" -#include "ui/widgets/buttons.h" -#include "ui/effects/ripple_animation.h" -#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/special_buttons.h" -#include "ui/unread_badge.h" #include "base/unixtime.h" #include "window/window_session_controller.h" -#include "window/window_controller.h" -#include "core/application.h" #include "main/main_session.h" #include "settings/settings_premium.h" #include "apiwrap.h" -#include "mainwindow.h" #include "api/api_peer_photo.h" -#include "chat_helpers/tabbed_panel.h" -#include "chat_helpers/tabbed_selector.h" #include "styles/style_boxes.h" #include "styles/style_info.h" -#include "styles/style_chat_helpers.h" -namespace Info { -namespace Profile { +namespace Info::Profile { namespace { auto MembersStatusText(int count) { @@ -83,293 +64,8 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) { : tr::lng_channel_status(tr::now); }; -void PickUntilBox(not_null box, Fn callback) { - box->setTitle(tr::lng_emoji_status_for_title()); - - const auto seconds = Ui::DefaultTimePickerValues(); - const auto phrases = ranges::views::all( - seconds - ) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector; - - const auto pickerCallback = Ui::TimePickerBox(box, seconds, phrases, 0); - - Ui::ConfirmBox(box, { - .confirmed = [=] { - callback(pickerCallback()); - box->closeBox(); - }, - .confirmText = tr::lng_emoji_status_for_submit(), - .cancelText = tr::lng_cancel(), - }); -} - } // namespace -BadgeView::BadgeView( - not_null parent, - const style::InfoPeerBadge &st, - not_null peer, - Fn animationPaused, - int customStatusLoopsLimit, - base::flags allowed) -: _parent(parent) -, _st(st) -, _peer(peer) -, _customStatusLoopsLimit(customStatusLoopsLimit) -, _allowed(allowed) -, _animationPaused(std::move(animationPaused)) { - rpl::combine( - BadgeValue(peer), - EmojiStatusIdValue(peer) - ) | rpl::start_with_next([=](Badge badge, DocumentId emojiStatusId) { - setBadge(badge, emojiStatusId); - }, _lifetime); -} - -Ui::RpWidget *BadgeView::widget() const { - return _view.data(); -} - -void BadgeView::setBadge(Badge badge, DocumentId emojiStatusId) { - if ((!_peer->session().premiumBadgesShown() && badge == Badge::Premium) - || !(_allowed & badge)) { - badge = Badge::None; - } - if (!(_allowed & badge)) { - badge = Badge::None; - } - if (badge != Badge::Premium) { - emojiStatusId = 0; - } - if (_badge == badge && _emojiStatusId == emojiStatusId) { - return; - } - _badge = badge; - _emojiStatusId = emojiStatusId; - _emojiStatus = nullptr; - _emojiStatusColored = nullptr; - _view.destroy(); - if (_badge == Badge::None) { - _updated.fire({}); - return; - } - _view.create(_parent); - _view->show(); - switch (_badge) { - case Badge::Verified: - case Badge::Premium: { - if (_emojiStatusId) { - using SizeTag = Data::CustomEmojiManager::SizeTag; - const auto tag = (_st.sizeTag == 2) - ? SizeTag::Isolated - : (_st.sizeTag == 1) - ? SizeTag::Large - : SizeTag::Normal; - _emojiStatus = _peer->owner().customEmojiManager().create( - _emojiStatusId, - [raw = _view.data()] { raw->update(); }, - tag); - if (_customStatusLoopsLimit > 0) { - _emojiStatus = std::make_unique( - std::move(_emojiStatus), - _customStatusLoopsLimit); - } - _emojiStatusColored = std::make_unique< - Ui::Text::CustomEmojiColored - >(); - const auto emoji = Data::FrameSizeFromTag(tag) - / style::DevicePixelRatio(); - _view->resize(emoji, emoji); - _view->paintRequest( - ) | rpl::start_with_next([=, check = _view.data()]{ - Painter p(check); - _emojiStatusColored->color = _st.premiumFg->c; - _emojiStatus->paint(p, { - .preview = st::windowBgOver->c, - .colored = _emojiStatusColored.get(), - .now = crl::now(), - .paused = _animationPaused && _animationPaused(), - }); - }, _view->lifetime()); - } else { - const auto icon = (_badge == Badge::Verified) - ? &_st.verified - : &_st.premium; - _view->resize(icon->size()); - _view->paintRequest( - ) | rpl::start_with_next([=, check = _view.data()]{ - Painter p(check); - icon->paint(p, 0, 0, check->width()); - }, _view->lifetime()); - } - } break; - case Badge::Scam: - case Badge::Fake: { - const auto fake = (_badge == Badge::Fake); - const auto size = Ui::ScamBadgeSize(fake); - const auto skip = st::infoVerifiedCheckPosition.x(); - _view->resize( - size.width() + 2 * skip, - size.height() + 2 * skip); - _view->paintRequest( - ) | rpl::start_with_next([=, badge = _view.data()]{ - Painter p(badge); - Ui::DrawScamBadge( - fake, - p, - badge->rect().marginsRemoved({ skip, skip, skip, skip }), - badge->width(), - st::attentionButtonFg); - }, _view->lifetime()); - } break; - } - - if (_badge != Badge::Premium || !_premiumClickCallback) { - _view->setAttribute(Qt::WA_TransparentForMouseEvents); - } else { - _view->setClickedCallback(_premiumClickCallback); - } - - _updated.fire({}); -} - -void BadgeView::setPremiumClickCallback(Fn callback) { - _premiumClickCallback = std::move(callback); - if (_view && _badge == Badge::Premium) { - if (!_premiumClickCallback) { - _view->setAttribute(Qt::WA_TransparentForMouseEvents); - } else { - _view->setAttribute(Qt::WA_TransparentForMouseEvents, false); - _view->setClickedCallback(_premiumClickCallback); - } - } -} - -rpl::producer<> BadgeView::updated() const { - return _updated.events(); -} - -void BadgeView::move(int left, int top, int bottom) { - if (!_view) { - return; - } - const auto star = !_emojiStatus - && (_badge == Badge::Premium || _badge == Badge::Verified); - const auto fake = !_emojiStatus && !star; - const auto skip = fake ? 0 : _st.position.x(); - const auto badgeLeft = left + skip; - const auto badgeTop = top - + (star - ? _st.position.y() - : (bottom - top - _view->height()) / 2); - _view->moveToLeft(badgeLeft, badgeTop); -} - -void EmojiStatusPanel::show( - not_null controller, - not_null button) { - const auto self = controller->session().user(); - const auto &statuses = controller->session().data().emojiStatuses(); - const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent); - const auto &other = statuses.list(Data::EmojiStatuses::Type::Default); - auto list = statuses.list(Data::EmojiStatuses::Type::Colored); - list.insert(begin(list), 0); - list.reserve(list.size() + recent.size() + other.size() + 1); - for (const auto &id : ranges::views::concat(recent, other)) { - if (!ranges::contains(list, id)) { - list.push_back(id); - } - } - if (!ranges::contains(list, self->emojiStatusId())) { - list.push_back(self->emojiStatusId()); - } - if (!_panel) { - create(controller); - - const auto weak = Ui::MakeWeak(button.get()); - _panel->shownValue( - ) | rpl::filter([=](bool shown) { - return !shown && weak; - }) | rpl::start_with_next([=] { - button->removeEventFilter(_panel.get()); - }, _panel->lifetime()); - } - _panel->selector()->provideRecentEmoji(list); - const auto parent = _panel->parentWidget(); - const auto global = button->mapToGlobal(QPoint()); - const auto local = parent->mapFromGlobal(global); - _panel->moveTopRight( - local.y() + button->height(), - local.x() + button->width() * 3); - _panel->toggleAnimated(); - button->installEventFilter(_panel.get()); -} - -void EmojiStatusPanel::create( - not_null controller) { - using Selector = ChatHelpers::TabbedSelector; - _panel = base::make_unique_q( - controller->window().widget()->bodyWidget(), - controller, - object_ptr( - nullptr, - controller, - Window::GifPauseReason::Layer, - ChatHelpers::TabbedSelector::Mode::EmojiStatus)); - _panel->setDropDown(true); - _panel->setDesiredHeightValues( - 1., - st::emojiPanMinHeight / 2, - st::emojiPanMinHeight); - _panel->hide(); - _panel->selector()->setAllowEmojiWithoutPremium(false); - - struct Chosen { - DocumentId id = 0; - TimeId until = 0; - Ui::MessageSendingAnimationFrom animation; - }; - - _panel->selector()->contextMenuRequested( - ) | rpl::start_with_next([=] { - _panel->selector()->showMenuWithType(SendMenu::Type::Scheduled); - }, _panel->lifetime()); - - auto statusChosen = _panel->selector()->customEmojiChosen( - ) | rpl::map([=](Selector::FileChosen data) { - return Chosen{ - .id = data.document->id, - .until = data.options.scheduled, - .animation = data.messageSendingFrom, - }; - }); - - auto emojiChosen = _panel->selector()->emojiChosen( - ) | rpl::map([=](Selector::EmojiChosen data) { - return Chosen{ .animation = data.messageSendingFrom }; - }); - - rpl::merge( - std::move(statusChosen), - std::move(emojiChosen) - ) | rpl::start_with_next([=](const Chosen chosen) { - if (chosen.until == ChatHelpers::TabbedSelector::kPickCustomTimeId) { - controller->show(Box(PickUntilBox, [=](TimeId seconds) { - controller->session().data().emojiStatuses().set( - chosen.id, - base::unixtime::now() + seconds); - })); - } else { - controller->session().data().emojiStatuses().set( - chosen.id, - chosen.until); - _panel->hideAnimated(); - } - }, _panel->lifetime()); - - _panel->selector()->showPromoForPremiumEmoji(); -} - Cover::Cover( QWidget *parent, not_null peer, @@ -393,14 +89,19 @@ Cover::Cover( + st::infoProfilePhotoBottom) , _controller(controller) , _peer(peer) +, _emojiStatusPanel(peer->isSelf() + ? std::make_unique() + : nullptr) , _badge( - this, - st::infoPeerBadge, - peer, - [=] { - return controller->isGifPausedAtLeastFor( - Window::GifPauseReason::Layer); - }) + std::make_unique( + this, + st::infoPeerBadge, + peer, + _emojiStatusPanel.get(), + [=] { + return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); + })) , _userpic( this, controller, @@ -423,14 +124,14 @@ Cover::Cover( _status->setAttribute(Qt::WA_TransparentForMouseEvents); } - _badge.setPremiumClickCallback([=] { - if (_peer->isSelf()) { - _emojiStatusPanel.show(_controller, _badge.widget()); + _badge->setPremiumClickCallback([=] { + if (const auto panel = _emojiStatusPanel.get()) { + panel->show(_controller, _badge->widget(), _badge->sizeTag()); } else { ::Settings::ShowEmojiStatusPremium(_controller, _peer); } }); - _badge.updated() | rpl::start_with_next([=] { + _badge->updated() | rpl::start_with_next([=] { refreshNameGeometry(width()); }, _name->lifetime()); @@ -563,15 +264,15 @@ void Cover::refreshNameGeometry(int newWidth) { auto nameWidth = newWidth - nameLeft - st::infoProfileNameRight; - if (const auto width = _badge.widget() ? _badge.widget()->width() : 0) { - nameWidth -= st::infoVerifiedCheckPosition.x() + width; + if (const auto widget = _badge->widget()) { + nameWidth -= st::infoVerifiedCheckPosition.x() + widget->width(); } _name->resizeToNaturalWidth(nameWidth); _name->moveToLeft(nameLeft, nameTop, newWidth); const auto badgeLeft = nameLeft + _name->width(); const auto badgeTop = nameTop; const auto badgeBottom = nameTop + _name->height(); - _badge.move(badgeLeft, badgeTop, badgeBottom); + _badge->move(badgeLeft, badgeTop, badgeBottom); } void Cover::refreshStatusGeometry(int newWidth) { @@ -585,5 +286,4 @@ void Cover::refreshStatusGeometry(int newWidth) { newWidth); } -} // namespace Profile -} // namespace Info +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 9131acf06..5189f53b0 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -8,25 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/wrap/padding_wrap.h" -#include "ui/widgets/checkbox.h" #include "base/timer.h" -#include "base/flags.h" namespace Window { class SessionController; } // namespace Window -namespace style { -struct InfoToggle; -struct InfoPeerBadge; -} // namespace style - -namespace ChatHelpers { -class TabbedPanel; -} // namespace ChatHelpers - namespace Ui { -class AbstractButton; class UserpicButton; class FlatLabel; template @@ -42,66 +30,10 @@ class Controller; class Section; } // namespace Info -namespace Info { -namespace Profile { +namespace Info::Profile { -enum class Badge { - None = 0x00, - Verified = 0x01, - Premium = 0x02, - Scam = 0x04, - Fake = 0x08, -}; -inline constexpr bool is_flag_type(Badge) { return true; } - -class BadgeView final { -public: - BadgeView( - not_null parent, - const style::InfoPeerBadge &st, - not_null peer, - Fn animationPaused, - int customStatusLoopsLimit = 0, - base::flags allowed = base::flags::from_raw(-1)); - - [[nodiscard]] Ui::RpWidget *widget() const; - - void setPremiumClickCallback(Fn callback); - [[nodiscard]] rpl::producer<> updated() const; - void move(int left, int top, int bottom); - -private: - void setBadge(Badge badge, DocumentId emojiStatusId); - - const not_null _parent; - const style::InfoPeerBadge &_st; - const not_null _peer; - const int _customStatusLoopsLimit = 0; - DocumentId _emojiStatusId = 0; - std::unique_ptr _emojiStatus; - std::unique_ptr _emojiStatusColored; - base::flags _allowed; - Badge _badge = Badge(); - Fn _premiumClickCallback; - Fn _animationPaused; - object_ptr _view = { nullptr }; - rpl::event_stream<> _updated; - rpl::lifetime _lifetime; - -}; - -class EmojiStatusPanel final { -public: - void show( - not_null controller, - not_null button); - -private: - void create(not_null controller); - - base::unique_qptr _panel; - -}; +class EmojiStatusPanel; +class Badge; class Cover final : public Ui::FixedHeightWidget { public: @@ -114,15 +46,14 @@ public: not_null peer, not_null controller, rpl::producer title); + ~Cover(); Cover *setOnlineCount(rpl::producer &&count); - rpl::producer
showSection() const { + [[nodiscard]] rpl::producer
showSection() const { return _showSection.events(); } - ~Cover(); - private: void setupChildGeometry(); void initViewers(rpl::producer title); @@ -133,8 +64,8 @@ private: const not_null _controller; const not_null _peer; - BadgeView _badge; - EmojiStatusPanel _emojiStatusPanel; + const std::unique_ptr _emojiStatusPanel; + const std::unique_ptr _badge; int _onlineCount = 0; object_ptr _userpic; @@ -147,5 +78,4 @@ private: }; -} // namespace Profile -} // namespace Info +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp new file mode 100644 index 000000000..238f5d35a --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp @@ -0,0 +1,322 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "info/profile/info_profile_emoji_status_panel.h" + +#include "data/data_user.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "data/data_emoji_statuses.h" +#include "data/stickers/data_custom_emoji.h" +#include "history/view/reactions/history_view_reactions_animation.h" +#include "lang/lang_keys.h" +#include "menu/menu_send.h" // SendMenu::Type. +#include "ui/boxes/confirm_box.h" +#include "ui/boxes/time_picker_box.h" +#include "ui/text/format_values.h" +#include "base/unixtime.h" +#include "window/window_session_controller.h" +#include "window/window_controller.h" +#include "main/main_session.h" +#include "mainwindow.h" +#include "chat_helpers/tabbed_panel.h" +#include "chat_helpers/tabbed_selector.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_info.h" +#include "styles/style_chat.h" + +namespace Info::Profile { +namespace { + +void PickUntilBox(not_null box, Fn callback) { + box->setTitle(tr::lng_emoji_status_for_title()); + + const auto seconds = Ui::DefaultTimePickerValues(); + const auto phrases = ranges::views::all( + seconds + ) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector; + + const auto pickerCallback = Ui::TimePickerBox(box, seconds, phrases, 0); + + Ui::ConfirmBox(box, { + .confirmed = [=] { + callback(pickerCallback()); + box->closeBox(); + }, + .confirmText = tr::lng_emoji_status_for_submit(), + .cancelText = tr::lng_cancel(), + }); +} + +} // namespace + +class EmojiStatusPanel::Animation { +public: + Animation( + not_null body, + not_null owner, + HistoryView::Reactions::AnimationArgs &&args, + Fn repaint, + Data::CustomEmojiSizeTag tag); + + [[nodiscard]] not_null layer(); + [[nodiscard]] bool finished() const; + + void repaint(); + bool paintBadgeFrame(not_null widget); + +private: + const int _flySize = 0; + HistoryView::Reactions::Animation _fly; + Ui::RpWidget _layer; + QRect _area; + bool _areaUpdated = false; + QPointer _target; + +}; + +[[nodiscard]] int ComputeFlySize(Data::CustomEmojiSizeTag tag) { + using Tag = Data::CustomEmojiSizeTag; + if (tag == Tag::Normal) { + return st::reactionInlineImage; + } + return int(base::SafeRound( + (st::reactionInlineImage * Data::FrameSizeFromTag(tag) + / float64(Data::FrameSizeFromTag(Tag::Normal))))); +} + +EmojiStatusPanel::Animation::Animation( + not_null body, + not_null owner, + HistoryView::Reactions::AnimationArgs &&args, + Fn repaint, + Data::CustomEmojiSizeTag tag) +: _flySize(ComputeFlySize(tag)) +, _fly( + owner, + std::move(args), + std::move(repaint), + _flySize, + tag) +, _layer(body) { + body->sizeValue() | rpl::start_with_next([=](QSize size) { + _layer.setGeometry(QRect(QPoint(), size)); + }, _layer.lifetime()); + + _layer.paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + const auto target = _target.data(); + if (!target || !target->isVisible()) { + return; + } + auto p = QPainter(&_layer); + + const auto rect = Ui::MapFrom(&_layer, target, target->rect()); + const auto skipx = (rect.width() - _flySize) / 2; + const auto skipy = (rect.height() - _flySize) / 2; + const auto area = _fly.paintGetArea( + p, + QPoint(), + QRect( + rect.topLeft() + QPoint(skipx, skipy), + QSize(_flySize, _flySize)), + st::infoPeerBadge.premiumFg->c, + clip, + crl::now()); + if (_areaUpdated || _area.isEmpty()) { + _area = area; + } else { + _area = _area.united(area); + } + }, _layer.lifetime()); + + _layer.setAttribute(Qt::WA_TransparentForMouseEvents); + _layer.show(); +} + +not_null EmojiStatusPanel::Animation::layer() { + return &_layer; +} + +bool EmojiStatusPanel::Animation::finished() const { + if (const auto target = _target.data()) { + return _fly.finished() || !target->isVisible(); + } + return true; +} + +void EmojiStatusPanel::Animation::repaint() { + if (_area.isEmpty()) { + _layer.update(); + } else { + _layer.update(_area); + _areaUpdated = true; + } +} + +bool EmojiStatusPanel::Animation::paintBadgeFrame( + not_null widget) { + _target = widget; + return !_fly.finished(); +} + +EmojiStatusPanel::EmojiStatusPanel() = default; + +EmojiStatusPanel::~EmojiStatusPanel() = default; + +void EmojiStatusPanel::show( + not_null controller, + not_null button, + Data::CustomEmojiSizeTag animationSizeTag) { + const auto self = controller->session().user(); + const auto &statuses = controller->session().data().emojiStatuses(); + const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent); + const auto &other = statuses.list(Data::EmojiStatuses::Type::Default); + auto list = statuses.list(Data::EmojiStatuses::Type::Colored); + list.insert(begin(list), 0); + list.reserve(list.size() + recent.size() + other.size() + 1); + for (const auto &id : ranges::views::concat(recent, other)) { + if (!ranges::contains(list, id)) { + list.push_back(id); + } + } + if (!ranges::contains(list, self->emojiStatusId())) { + list.push_back(self->emojiStatusId()); + } + if (!_panel) { + create(controller); + + _panel->shownValue( + ) | rpl::filter([=] { + return (_panelButton != nullptr); + }) | rpl::start_with_next([=](bool shown) { + if (shown) { + _panelButton->installEventFilter(_panel.get()); + } else { + _panelButton->removeEventFilter(_panel.get()); + } + }, _panel->lifetime()); + } + if (const auto previous = _panelButton.data()) { + if (previous != button) { + previous->removeEventFilter(_panel.get()); + } + } + _panelButton = button; + _animationSizeTag = animationSizeTag; + _panel->selector()->provideRecentEmoji(list); + const auto parent = _panel->parentWidget(); + const auto global = button->mapToGlobal(QPoint()); + const auto local = parent->mapFromGlobal(global); + _panel->moveTopRight( + local.y() + button->height(), + local.x() + button->width() * 3); + _panel->toggleAnimated(); +} + +bool EmojiStatusPanel::paintBadgeFrame(not_null widget) { + if (!_animation) { + return false; + } else if (_animation->paintBadgeFrame(widget)) { + return true; + } + InvokeQueued(_animation->layer(), [=] { _animation = nullptr; }); + return false; +} + +void EmojiStatusPanel::create( + not_null controller) { + using Selector = ChatHelpers::TabbedSelector; + const auto body = controller->window().widget()->bodyWidget(); + _panel = base::make_unique_q( + body, + controller, + object_ptr( + nullptr, + controller, + Window::GifPauseReason::Layer, + ChatHelpers::TabbedSelector::Mode::EmojiStatus)); + _panel->setDropDown(true); + _panel->setDesiredHeightValues( + 1., + st::emojiPanMinHeight / 2, + st::emojiPanMinHeight); + _panel->hide(); + _panel->selector()->setAllowEmojiWithoutPremium(false); + + struct Chosen { + DocumentId id = 0; + TimeId until = 0; + Ui::MessageSendingAnimationFrom animation; + }; + + _panel->selector()->contextMenuRequested( + ) | rpl::start_with_next([=] { + _panel->selector()->showMenuWithType(SendMenu::Type::Scheduled); + }, _panel->lifetime()); + + auto statusChosen = _panel->selector()->customEmojiChosen( + ) | rpl::map([=](Selector::FileChosen data) { + return Chosen{ + .id = data.document->id, + .until = data.options.scheduled, + .animation = data.messageSendingFrom, + }; + }); + + auto emojiChosen = _panel->selector()->emojiChosen( + ) | rpl::map([=](Selector::EmojiChosen data) { + return Chosen{ .animation = data.messageSendingFrom }; + }); + + const auto set = [=](Chosen chosen) { + Expects(chosen.until != Selector::kPickCustomTimeId); + + const auto owner = &controller->session().data(); + startAnimation(owner, body, chosen.id, chosen.animation); + owner->emojiStatuses().set(chosen.id, chosen.until); + }; + + rpl::merge( + std::move(statusChosen), + std::move(emojiChosen) + ) | rpl::start_with_next([=](const Chosen chosen) { + if (chosen.until == Selector::kPickCustomTimeId) { + controller->show(Box(PickUntilBox, [=](TimeId seconds) { + set({ chosen.id, base::unixtime::now() + seconds }); + })); + } else { + set(chosen); + _panel->hideAnimated(); + } + }, _panel->lifetime()); + + _panel->selector()->showPromoForPremiumEmoji(); +} + +void EmojiStatusPanel::startAnimation( + not_null owner, + not_null body, + DocumentId statusId, + Ui::MessageSendingAnimationFrom from) { + if (!_panelButton) { + return; + } + auto args = HistoryView::Reactions::AnimationArgs{ + .id = { { statusId } }, + .flyIcon = from.frame, + .flyFrom = body->mapFromGlobal(from.globalStartGeometry), + }; + _animation = std::make_unique( + body, + &owner->reactions(), + std::move(args), + [=] { _animation->repaint(); }, + _animationSizeTag); +} + +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h new file mode 100644 index 000000000..d0ab1b74b --- /dev/null +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h @@ -0,0 +1,67 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/unique_qptr.h" + +namespace Data { +class Session; +enum class CustomEmojiSizeTag : uchar; +} // namespace Data + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { +struct MessageSendingAnimationFrom; +class RpWidget; +} // namespace Ui + +namespace Ui::Text { +class CustomEmoji; +struct CustomEmojiPaintContext; +} // namespace Ui::Text + +namespace ChatHelpers { +class TabbedPanel; +} // namespace ChatHelpers + +namespace Info::Profile { + +class EmojiStatusPanel final { +public: + EmojiStatusPanel(); + ~EmojiStatusPanel(); + + void show( + not_null controller, + not_null button, + Data::CustomEmojiSizeTag animationSizeTag = {}); + + bool paintBadgeFrame(not_null widget); + +private: + class Animation; + + void create(not_null controller); + + void startAnimation( + not_null owner, + not_null body, + DocumentId statusId, + Ui::MessageSendingAnimationFrom from); + + base::unique_qptr _panel; + QPointer _panelButton; + std::unique_ptr _animation; + Data::CustomEmojiSizeTag _animationSizeTag = {}; + +}; + +} // namespace Info::Profile diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 33a9d77e9..eec7d74f5 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -93,12 +93,12 @@ object_ptr InnerWidget::setupContent( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { auto min = (request.ymin < 0) ? request.ymin - : mapFromGlobal(_members->mapToGlobal({ 0, request.ymin })).y(); + : MapFrom(this, _members, QPoint(0, request.ymin)).y(); auto max = (request.ymin < 0) - ? mapFromGlobal(_members->mapToGlobal({ 0, 0 })).y() + ? MapFrom(this, _members, QPoint()).y() : (request.ymax < 0) ? request.ymax - : mapFromGlobal(_members->mapToGlobal({ 0, request.ymax })).y(); + : MapFrom(this, _members, QPoint(0, request.ymax)).y(); _scrollToRequests.fire({ min, max }); }, _members->lifetime()); _cover->setOnlineCount(_members->onlineCountValue()); diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 5a5434a57..28b606cab 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_values.h" -#include "info/profile/info_profile_cover.h" +#include "info/profile/info_profile_badge.h" #include "core/application.h" #include "core/click_handler_types.h" #include "countries/countries_instance.h" @@ -456,7 +456,7 @@ rpl::producer FullReactionsCountValue( } template -rpl::producer BadgeValueFromFlags(Peer peer) { +rpl::producer BadgeValueFromFlags(Peer peer) { return rpl::combine( Data::PeerFlagsValue( peer, @@ -464,24 +464,24 @@ rpl::producer BadgeValueFromFlags(Peer peer) { Data::PeerPremiumValue(peer) ) | rpl::map([=](base::flags value, bool premium) { return (value & Flag::Scam) - ? Badge::Scam + ? BadgeType::Scam : (value & Flag::Fake) - ? Badge::Fake + ? BadgeType::Fake : (value & Flag::Verified) - ? Badge::Verified + ? BadgeType::Verified : premium - ? Badge::Premium - : Badge::None; + ? BadgeType::Premium + : BadgeType::None; }); } -rpl::producer BadgeValue(not_null peer) { +rpl::producer BadgeValue(not_null peer) { if (const auto user = peer->asUser()) { return BadgeValueFromFlags(user); } else if (const auto channel = peer->asChannel()) { return BadgeValueFromFlags(channel); } - return rpl::single(Badge::None); + return rpl::single(BadgeType::None); } rpl::producer EmojiStatusIdValue(not_null peer) { diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index c388c66ec..b7ec2d344 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -90,8 +90,8 @@ rpl::producer> MigratedOrMeValue( [[nodiscard]] rpl::producer FullReactionsCountValue( not_null peer); -enum class Badge; -[[nodiscard]] rpl::producer BadgeValue(not_null peer); +enum class BadgeType; +[[nodiscard]] rpl::producer BadgeValue(not_null peer); [[nodiscard]] rpl::producer EmojiStatusIdValue( not_null peer); diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index c35124a60..303aa3dd7 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -36,7 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_premium_limits.h" #include "dialogs/ui/dialogs_layout.h" #include "info/profile/info_profile_values.h" -#include "info/profile/info_profile_cover.h" +#include "info/profile/info_profile_badge.h" #include "lang/lang_keys.h" #include "main/main_account.h" #include "main/main_session.h" @@ -82,7 +82,7 @@ private: rpl::event_stream _premiumWidth; QPointer _unread; - Info::Profile::BadgeView _badge; + Info::Profile::Badge _badge; }; @@ -99,9 +99,10 @@ ComposedBadge::ComposedBadge( this, st::settingsInfoPeerBadge, session->user(), + nullptr, std::move(animationPaused), kPlayStatusLimit, - Info::Profile::Badge::Premium) { + Info::Profile::BadgeType::Premium) { if (hasUnread) { _unread = CreateUnread(this, rpl::single( rpl::empty diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 0db0b5300..3e8a92489 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -30,7 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/special_buttons.h" -#include "info/profile/info_profile_cover.h" +#include "info/profile/info_profile_badge.h" +#include "info/profile/info_profile_emoji_status_panel.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_cloud_themes.h" @@ -85,8 +86,8 @@ private: const not_null _controller; const not_null _user; - Info::Profile::BadgeView _badge; Info::Profile::EmojiStatusPanel _emojiStatusPanel; + Info::Profile::Badge _badge; object_ptr _userpic; object_ptr _name = { nullptr }; @@ -110,12 +111,13 @@ Cover::Cover( this, st::infoPeerBadge, user, + &_emojiStatusPanel, [=] { return controller->isGifPausedAtLeastFor( Window::GifPauseReason::Layer); }, 0, // customStatusLoopsLimit - Info::Profile::Badge::Premium) + Info::Profile::BadgeType::Premium) , _userpic( this, controller, @@ -145,7 +147,10 @@ Cover::Cover( }, _userpic->lifetime()); _badge.setPremiumClickCallback([=] { - _emojiStatusPanel.show(_controller, _badge.widget()); + _emojiStatusPanel.show( + _controller, + _badge.widget(), + _badge.sizeTag()); }); _badge.updated() | rpl::start_with_next([=] { refreshNameGeometry(width()); diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 5c63b948a..c8cf2aeed 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -208,7 +208,8 @@ using Order = std::vector; u"faster_download"_q, u"voice_to_text"_q, u"no_ads"_q, - u"unique_reactions"_q, + u"emoji_status"_q, + u"infinite_reactions"_q, u"premium_stickers"_q, u"animated_emoji"_q, u"advanced_chat_management"_q, @@ -264,12 +265,21 @@ using Order = std::vector; }, }, { - u"unique_reactions"_q, + u"emoji_status"_q, Entry{ &st::settingsPremiumIconLike, - tr::lng_premium_summary_subtitle_unique_reactions(), - tr::lng_premium_summary_about_unique_reactions(), - PremiumPreview::Reactions, + tr::lng_premium_summary_subtitle_emoji_status(), + tr::lng_premium_summary_about_emoji_status(), + PremiumPreview::EmojiStatus, + }, + }, + { + u"infinite_reactions"_q, + Entry{ + &st::settingsPremiumIconLike, + tr::lng_premium_summary_subtitle_infinite_reactions(), + tr::lng_premium_summary_about_infinite_reactions(), + PremiumPreview::InfiniteReactions, }, }, { diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 9797b2cfb..efdbe2317 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -343,26 +343,6 @@ 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; - const auto &allowed = Data::PeerAllowedReactions(peer); - if (!allowed.some.empty()) { - for (const auto &reaction : list) { - if (reaction.premium - && !ranges::contains(allowed.some, reaction.id)) { - result.emplace(reaction.id, type); - } - } - } - return result; -} - bool ShowReactPremiumError( not_null controller, not_null item, @@ -379,22 +359,8 @@ bool ShowReactPremiumError( return false; } } - ShowPremiumPreviewBox( - controller, - PremiumPreview::Reactions, - ExtractDisabledReactions(item->history()->peer, list)); + ShowPremiumPreviewBox(controller, PremiumPreview::InfiniteReactions); return true; } -void ShowPremiumPromoBox( - not_null controller, - not_null item) { - const auto &list = controller->session().data().reactions().list( - Data::Reactions::Type::Active); - ShowPremiumPreviewBox( - controller, - PremiumPreview::Reactions, - ExtractDisabledReactions(item->history()->peer, list)); -} - } // namespace Window diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index a89162d48..81cc3b12e 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -219,8 +219,4 @@ private: not_null item, const Data::ReactionId &id); -void ShowPremiumPromoBox( - not_null controller, - not_null item); - } // namespace Window diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index bf648265c..011b06919 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -34,7 +34,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "settings/settings_calls.h" #include "settings/settings_information.h" -#include "info/profile/info_profile_cover.h" +#include "info/profile/info_profile_badge.h" +#include "info/profile/info_profile_emoji_status_panel.h" #include "base/qt_signal_producer.h" #include "boxes/about_box.h" #include "ui/boxes/confirm_box.h" @@ -338,14 +339,15 @@ MainMenu::MainMenu( Ui::UserpicButton::Role::Custom, st::mainMenuUserpic) , _toggleAccounts(this) -, _badge(std::make_unique( +, _emojiStatusPanel(std::make_unique()) +, _badge(std::make_unique( this, st::settingsInfoPeerBadge, controller->session().user(), + _emojiStatusPanel.get(), [=] { return controller->isGifPausedAtLeastFor(GifPauseReason::Layer); }, kPlayStatusLimit, - Info::Profile::Badge::Premium)) -, _emojiStatusPanel(std::make_unique()) + Info::Profile::BadgeType::Premium)) , _scroll(this, st::defaultSolidScroll) , _inner(_scroll->setOwnedWidget( object_ptr(_scroll.data()))) @@ -438,7 +440,10 @@ MainMenu::MainMenu( moveBadge(); }, lifetime()); _badge->setPremiumClickCallback([=] { - _emojiStatusPanel->show(_controller, _badge->widget()); + _emojiStatusPanel->show( + _controller, + _badge->widget(), + _badge->sizeTag()); }); _controller->session().downloaderTaskFinished( diff --git a/Telegram/SourceFiles/window/window_main_menu.h b/Telegram/SourceFiles/window/window_main_menu.h index 2e05ef3c2..b49240c36 100644 --- a/Telegram/SourceFiles/window/window_main_menu.h +++ b/Telegram/SourceFiles/window/window_main_menu.h @@ -29,7 +29,7 @@ class SlideWrap; } // namespace Ui namespace Info::Profile { -class BadgeView; +class Badge; class EmojiStatusPanel; } // namespace Info::Profile @@ -77,8 +77,8 @@ private: Ui::Text::String _name; int _nameVersion = 0; object_ptr _toggleAccounts; - std::unique_ptr _badge; std::unique_ptr _emojiStatusPanel; + std::unique_ptr _badge; object_ptr _resetScaleButton = { nullptr }; object_ptr _scroll; not_null _inner; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 4ec399f16..12b8b3804 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 4ec399f169e9308cd5da194b6fa2104578c39e45 +Subproject commit 12b8b3804c1b458c124ca150de4320e3fda5035e