From fecddb52036e61481b3152f9dcbaa6744e42c002 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Jan 2025 14:34:31 +0400 Subject: [PATCH] Allow wearing collectibles from emoji status. --- Telegram/Resources/langs/lang.strings | 1 + .../boxes/peers/edit_forum_topic_box.cpp | 6 +- .../boxes/peers/edit_peer_reactions.cpp | 4 +- .../chat_helpers/chat_helpers.style | 2 + .../chat_helpers/compose/compose_features.h | 1 + .../chat_helpers/emoji_list_widget.cpp | 158 +++++++++++++++--- .../chat_helpers/emoji_list_widget.h | 29 +++- .../chat_helpers/stickers_list_footer.cpp | 2 + .../chat_helpers/tabbed_selector.cpp | 2 +- .../chat_helpers/tabbed_selector.h | 3 +- .../SourceFiles/data/data_emoji_statuses.cpp | 32 ++++ .../SourceFiles/data/data_emoji_statuses.h | 4 + .../data/stickers/data_stickers.cpp | 19 +++ .../SourceFiles/data/stickers/data_stickers.h | 5 + Telegram/SourceFiles/history/history.cpp | 11 ++ .../view/media/history_view_unique_gift.cpp | 4 +- .../history_view_reactions_selector.cpp | 2 +- .../info_profile_emoji_status_panel.cpp | 40 +++-- .../profile/info_profile_emoji_status_panel.h | 9 +- .../info_userpic_emoji_builder_widget.cpp | 2 +- .../SourceFiles/media/view/media_view.style | 1 + .../storage/serialize_document.cpp | 3 +- .../SourceFiles/storage/storage_account.cpp | 6 +- 23 files changed, 278 insertions(+), 68 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f32d3dff8..81f4f6352 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2373,6 +2373,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_stickers_add" = "Choose sticker set"; "lng_group_emoji" = "Select Emoji Pack"; "lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group."; +"lng_collectible_emoji" = "Collectibles"; "lng_premium" = "Premium"; "lng_premium_free" = "Free"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index be668fcd3..fe675532a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -265,7 +265,7 @@ struct IconSelector { const auto manager = &controller->session().data().customEmojiManager(); auto factory = [=](DocumentId id, Fn repaint) - -> std::unique_ptr { + -> std::unique_ptr { const auto tag = Data::CustomEmojiManager::SizeTag::Large; if (id == kDefaultIconId) { return std::make_unique( @@ -288,7 +288,7 @@ struct IconSelector { .show = controller->uiShow(), .mode = EmojiListWidget::Mode::TopicIcon, .paused = Window::PausedIn(controller, PauseReason::Layer), - .customRecentList = recent(), + .customRecentList = DocumentListToRecent(recent()), .customRecentFactory = std::move(factory), .st = &st::reactPanelEmojiPan, }), @@ -297,7 +297,7 @@ struct IconSelector { icons->requestDefaultIfUnknown(); icons->defaultUpdates( ) | rpl::start_with_next([=] { - selector->provideRecent(recent()); + selector->provideRecent(DocumentListToRecent(recent())); }, selector->lifetime()); placeFooter(selector->createFooter()); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index f7b1a6b4c..96ed8c949 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/event_filter.h" +#include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/ui_integration.h" @@ -491,7 +492,8 @@ object_ptr AddReactionsSelector( panelList.erase( ranges::remove(panelList, paid->selectAnimation->id), end(panelList)); - panel->selector()->provideRecentEmoji(panelList); + panel->selector()->provideRecentEmoji( + ChatHelpers::DocumentListToRecent(panelList)); panel->setDesiredHeightValues( 1., st::emojiPanMinHeight / 2, diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 4b66b507f..2ad236013 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -40,6 +40,7 @@ TabbedSearch { ComposeIcons { settings: icon; + collectibles: icon; recent: icon; recentActive: icon; @@ -587,6 +588,7 @@ sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_delete", roun defaultComposeIcons: ComposeIcons { settings: icon {{ "emoji/emoji_settings", emojiIconFg }}; + collectibles: icon {{ "menu/unique", emojiIconFg }}; recent: icon {{ "emoji/emoji_recent", emojiIconFg }}; recentActive: icon {{ "emoji/emoji_recent", emojiSubIconFgActive }}; diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h index 2f33d8163..dedf4ff06 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h @@ -18,6 +18,7 @@ struct ComposeFeatures { bool attachBotsMenu : 1 = true; bool inlineBots : 1 = true; bool megagroupSet : 1 = true; + bool collectibleStatus : 1 = false; bool stickersSettings : 1 = true; bool openStickerSets : 1 = true; bool autocompleteHashtags : 1 = true; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 1e35337b8..26c650bba 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/sticker_set_box.h" #include "lang/lang_keys.h" #include "layout/layout_position.h" +#include "data/data_emoji_statuses.h" #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_channel.h" @@ -134,6 +135,7 @@ struct EmojiListWidget::CustomEmojiInstance { }; struct EmojiListWidget::RecentOne { + std::shared_ptr collectible; Ui::Text::CustomEmoji *custom = nullptr; RecentEmojiId id; mutable QImage premiumLock; @@ -447,6 +449,13 @@ void EmojiColorPicker::drawVariant(QPainter &p, int variant) { w.y() + _innerPosition.y()); } +std::vector DocumentListToRecent( + const std::vector &documents) { + return documents | ranges::views::transform([](DocumentId id) { + return EmojiStatusId{ .documentId = id }; + }) | ranges::to_vector; +} + EmojiListWidget::EmojiListWidget( QWidget *parent, not_null controller, @@ -510,6 +519,11 @@ EmojiListWidget::EmojiListWidget( refreshCustom(); } }, lifetime()); + } else if (_mode == Mode::EmojiStatus && _features.collectibleStatus) { + session().data().emojiStatuses().collectiblesUpdates( + ) | rpl::start_with_next([=] { + refreshCustom(); + }, lifetime()); } _customSingleSize = Data::FrameSizeFromTag( @@ -656,7 +670,8 @@ void EmojiListWidget::applyNextSearchQuery() { void EmojiListWidget::showPreview() { if (const auto over = std::get_if(&_pressed)) { if (const auto custom = lookupCustomEmoji(over)) { - _show->showMediaPreview(custom->stickerSetOrigin(), custom); + const auto document = custom.document; + _show->showMediaPreview(document->stickerSetOrigin(), document); _previewShown = true; } } @@ -706,7 +721,7 @@ void EmojiListWidget::appendPremiumSearchResults() { } void EmojiListWidget::provideRecent( - const std::vector &customRecentList) { + const std::vector &customRecentList) { clearSelection(); fillRecentFrom(customRecentList); resizeToWidth(width()); @@ -1094,7 +1109,8 @@ void EmojiListWidget::fillRecent() { } } -void EmojiListWidget::fillRecentFrom(const std::vector &list) { +void EmojiListWidget::fillRecentFrom( + const std::vector &list) { const auto test = session().isTestMode(); _recent.clear(); _recent.reserve(list.size()); @@ -1113,11 +1129,17 @@ void EmojiListWidget::fillRecentFrom(const std::vector &list) { }); _recentCustomIds.emplace(fakeId); } else { + const auto documentId = id.collectible + ? id.collectible->documentId + : id.documentId; _recent.push_back({ - .custom = resolveCustomRecent(id), - .id = { RecentEmojiDocument{ .id = id, .test = test } }, + .collectible = id.collectible, + .custom = resolveCustomRecent(documentId), + .id = { + RecentEmojiDocument{ .id = documentId, .test = test }, + }, }); - _recentCustomIds.emplace(id); + _recentCustomIds.emplace(documentId); } } } @@ -1158,8 +1180,12 @@ void EmojiListWidget::fillRecentMenu( const auto over = OverEmoji{ section, index }; const auto emoji = lookupOverEmoji(&over); const auto custom = lookupCustomEmoji(&over); - if (custom && custom->sticker()) { - const auto sticker = custom->sticker(); + if (custom.collectible) { + return; + } + const auto document = custom.document; + if (document && document->sticker()) { + const auto sticker = document->sticker(); const auto emoji = sticker->alt; const auto setId = sticker->set.id; if (!emoji.isEmpty()) { @@ -1168,7 +1194,7 @@ void EmojiListWidget::fillRecentMenu( EntityType::CustomEmoji, 0, int(emoji.size()), - Data::SerializeCustomEmojiId(custom) + Data::SerializeCustomEmojiId(document) }); addAction(tr::lng_emoji_copy(tr::now), [=] { TextUtilities::SetClipboardText(data); @@ -1192,8 +1218,8 @@ void EmojiListWidget::fillRecentMenu( auto id = RecentEmojiId{ emoji }; if (custom) { id.data = RecentEmojiDocument{ - .id = custom->id, - .test = custom->session().isTestMode(), + .id = custom.document->id, + .test = custom.document->session().isTestMode(), }; } addAction(tr::lng_emoji_remove_recent(tr::now), crl::guard(this, [=] { @@ -1229,7 +1255,7 @@ void EmojiListWidget::fillEmojiStatusMenu( int section, int index) { const auto chosen = lookupCustomEmoji(index, section); - if (!chosen) { + if (!chosen || chosen.collectible) { return; } const auto selectWith = [=](TimeId scheduled) { @@ -1464,6 +1490,27 @@ void EmojiListWidget::drawCollapsedBadge( text); } +void EmojiListWidget::drawCollectible( + QPainter &p, + QPoint position, + Data::EmojiStatusCollectible *collectible) { + if (!collectible) { + return; + } + const auto inner = QRect(position, st::emojiPanArea); + auto gradient = QRadialGradient(inner.center(), inner.height() / 2); + gradient.setStops({ + { 0., collectible->centerColor }, + { 1., collectible->edgeColor }, + }); + p.setBrush(gradient); + p.setPen(Qt::NoPen); + p.drawRoundedRect( + inner, + st::emojiPanRadius, + st::emojiPanRadius); +} + void EmojiListWidget::drawRecent( QPainter &p, const ExpandingContext &context, @@ -1500,12 +1547,14 @@ void EmojiListWidget::drawRecent( auto q = Painter(&_premiumMarkFrameCache); _emojiPaintContext->position = QPoint(); + drawCollectible(q, position, recent.collectible.get()); custom->paint(q, *_emojiPaintContext); q.end(); p.drawImage(exactPosition, _premiumMarkFrameCache); } else { _emojiPaintContext->position = exactPosition; + drawCollectible(p, position, recent.collectible.get()); custom->paint(p, *_emojiPaintContext); } } else if (const auto emoji = std::get_if(&recent.id.data)) { @@ -1560,6 +1609,7 @@ void EmojiListWidget::drawCustom( _emojiPaintContext->position = position + _innerPosition + _customPosition; + drawCollectible(p, position, entry.collectible.get()); entry.custom->paint(p, *_emojiPaintContext); } @@ -1573,12 +1623,14 @@ bool EmojiListWidget::checkPickerHide() { return false; } -DocumentData *EmojiListWidget::lookupCustomEmoji( +EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji( const OverEmoji *over) const { - return over ? lookupCustomEmoji(over->index, over->section) : nullptr; + return over + ? lookupCustomEmoji(over->index, over->section) + : ResolvedCustom(); } -DocumentData *EmojiListWidget::lookupCustomEmoji( +EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji( int index, int section) const { if (_searchMode) { @@ -1586,22 +1638,23 @@ DocumentData *EmojiListWidget::lookupCustomEmoji( const auto document = std::get_if( &_searchResults[index].id.data); if (document) { - return session().data().document(document->id); + return { session().data().document(document->id) }; } } - return nullptr; + return {}; } else if (section == int(Section::Recent) && index < _recent.size()) { const auto document = std::get_if( &_recent[index].id.data); if (document) { - return session().data().document(document->id); + return { session().data().document(document->id) }; } } else if (section >= _staticCount && index < _custom[section - _staticCount].list.size()) { auto &set = _custom[section - _staticCount]; - return set.list[index].document; + auto &entry = set.list[index]; + return { entry.document, entry.collectible }; } - return nullptr; + return {}; } EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const { @@ -1643,9 +1696,11 @@ EmojiChosen EmojiListWidget::lookupChosen( } FileChosen EmojiListWidget::lookupChosen( - not_null custom, + ResolvedCustom custom, const OverEmoji *over, Api::SendOptions options) { + Expects(custom.document != nullptr); + _grabbingChosen = true; const auto guard = gsl::finally([&] { _grabbingChosen = false; }); const auto rect = over ? emojiRect(over->section, over->index) : QRect(); @@ -1655,13 +1710,14 @@ FileChosen EmojiListWidget::lookupChosen( ) : QRect(); return { - .document = custom, + .document = custom.document, .options = options, .messageSendingFrom = { .type = Ui::MessageSendingAnimationFrom::Type::Emoji, .globalStartGeometry = over ? mapToGlobal(emoji) : QRect(), .frame = over ? Ui::GrabWidgetToImage(this, emoji) : QImage(), }, + .collectible = custom.collectible, }; } @@ -1791,6 +1847,8 @@ void EmojiListWidget::displaySet(uint64 setId) { } else { return; } + } else if (setId == Data::Stickers::CollectibleSetId) { + return; } const auto &sets = session().data().stickers().sets(); auto it = sets.find(setId); @@ -1829,6 +1887,7 @@ void EmojiListWidget::removeSet(uint64 setId) { Assert(i != end(_custom)); const auto removeLocally = !_megagroupSet->canEditEmoji(); removeMegagroupSet(removeLocally); + } else if (setId == Data::Stickers::CollectibleSetId) { } else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) { checkHideWithBox(std::move(box)); } @@ -1932,6 +1991,8 @@ bool EmojiListWidget::hasRemoveButton(int index) const { return true; } return !set.list.empty() && _megagroupSet->canEditEmoji(); + } else if (set.id == Data::Stickers::CollectibleSetId) { + return false; } return set.canRemove && !set.premiumRequired; } @@ -1961,7 +2022,8 @@ bool EmojiListWidget::hasAddButton(int index) const { const auto &set = _custom[index - _staticCount]; return !set.canRemove && !set.premiumRequired - && set.id != Data::Stickers::MegagroupSetId; + && set.id != Data::Stickers::MegagroupSetId + && set.id != Data::Stickers::CollectibleSetId; } QRect EmojiListWidget::addButtonRect(int index) const { @@ -1990,8 +2052,9 @@ bool EmojiListWidget::hasButton(int index) const { } else if (index >= _staticCount && index < _staticCount + _custom.size()) { const auto &custom = _custom[index - _staticCount]; - return (custom.id != Data::Stickers::MegagroupSetId) - || custom.canRemove; + return (custom.id != Data::Stickers::CollectibleSetId) + && ((custom.id != Data::Stickers::MegagroupSetId) + || custom.canRemove); } return false; } @@ -2019,6 +2082,7 @@ QRect EmojiListWidget::buttonRect( auto EmojiListWidget::rightButton(int index) const -> const RightButton & { Expects(index >= _staticCount && index < _staticCount + _custom.size()); + return hasAddButton(index) ? _add : _custom[index - _staticCount].canRemove @@ -2304,6 +2368,7 @@ void EmojiListWidget::refreshCustom() { .premiumRequired = premium && premiumMayBeBought, }); }; + refreshEmojiStatusCollectibles(); refreshMegagroupStickers(push, GroupStickersPlace::Visible); for (const auto setId : owner->stickers().emojiSetsOrder()) { push(setId, true); @@ -2404,6 +2469,44 @@ not_null EmojiListWidget::resolveCustomRecent( ).first->second.emoji.get(); } +void EmojiListWidget::refreshEmojiStatusCollectibles() { + if (_mode != Mode::EmojiStatus || !_features.collectibleStatus) { + return; + } + const auto type = Data::EmojiStatuses::Type::Collectibles; + const auto &list = session().data().emojiStatuses().list(type); + if (list.empty()) { + return; + } + const auto setId = Data::Stickers::CollectibleSetId; + auto set = std::vector(); + set.reserve(list.size()); + for (const auto &status : list) { + const auto documentId = status.collectible + ? status.collectible->documentId + : status.documentId; + const auto document = session().data().document(documentId); + if (const auto sticker = document->sticker()) { + set.push_back({ + .collectible = status.collectible, + .custom = resolveCustomEmoji(document, setId), + .document = document, + .emoji = Ui::Emoji::Find(sticker->alt), + }); + } + } + const auto collectibles = session().data().stickers().collectibleSet(); + _custom.push_back({ + .id = setId, + .set = collectibles, + .thumbnailDocument = nullptr, + .title = collectibles->title, + .list = std::move(set), + .canRemove = false, + .premiumRequired = !session().premium(), + }); +} + void EmojiListWidget::refreshMegagroupStickers( Fn push, GroupStickersPlace place) { @@ -2638,8 +2741,11 @@ void EmojiListWidget::setSelected(OverState newSelected) { } else if (_previewShown && _pressed != _selected) { if (const auto over = std::get_if(&_selected)) { if (const auto custom = lookupCustomEmoji(over)) { + const auto document = custom.document; _pressed = _selected; - _show->showMediaPreview(custom->stickerSetOrigin(), custom); + _show->showMediaPreview( + document->stickerSetOrigin(), + document); } } } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index ff474ad23..4027ff683 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -83,12 +83,15 @@ enum class EmojiListMode { MessageEffects, }; +[[nodiscard]] std::vector DocumentListToRecent( + const std::vector &documents); + struct EmojiListDescriptor { std::shared_ptr show; EmojiListMode mode = EmojiListMode::Full; Fn customTextColor; Fn paused; - std::vector customRecentList; + std::vector customRecentList; Fn( DocumentId, Fn)> customRecentFactory; @@ -137,7 +140,7 @@ public: [[nodiscard]] rpl::producer<> jumpedToPremium() const; [[nodiscard]] rpl::producer<> escapes() const; - void provideRecent(const std::vector &customRecentList); + void provideRecent(const std::vector &customRecentList); void prepareExpanding(); void paintExpanding( @@ -186,6 +189,7 @@ private: bool collapsed = false; }; struct CustomOne { + std::shared_ptr collectible; not_null custom; not_null document; EmojiPtr emoji = nullptr; @@ -253,6 +257,14 @@ private: int finalHeight = 0; bool expanding = false; }; + struct ResolvedCustom { + DocumentData *document = nullptr; + std::shared_ptr collectible; + + explicit operator bool() const { + return document != nullptr; + } + }; template bool enumerateSections(Callback callback) const; @@ -271,6 +283,7 @@ private: Visible, Hidden, }; + void refreshEmojiStatusCollectibles(); void refreshMegagroupStickers( Fn push, GroupStickersPlace place); @@ -296,22 +309,26 @@ private: int index); [[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const; - [[nodiscard]] DocumentData *lookupCustomEmoji( + [[nodiscard]] ResolvedCustom lookupCustomEmoji( const OverEmoji *over) const; - [[nodiscard]] DocumentData *lookupCustomEmoji( + [[nodiscard]] ResolvedCustom lookupCustomEmoji( int index, int section) const; [[nodiscard]] EmojiChosen lookupChosen( EmojiPtr emoji, not_null over); [[nodiscard]] FileChosen lookupChosen( - not_null custom, + ResolvedCustom custom, const OverEmoji *over, Api::SendOptions options = Api::SendOptions()); void selectEmoji(EmojiChosen data); void selectCustom(FileChosen data); void paint(Painter &p, ExpandingContext context, QRect clip); void drawCollapsedBadge(QPainter &p, QPoint position, int count); + void drawCollectible( + QPainter &p, + QPoint position, + Data::EmojiStatusCollectible *collectible); void drawRecent( QPainter &p, const ExpandingContext &context, @@ -370,7 +387,7 @@ private: void repaintCustom(uint64 setId); void fillRecent(); - void fillRecentFrom(const std::vector &list); + void fillRecentFrom(const std::vector &list); [[nodiscard]] not_null resolveCustomEmoji( not_null document, uint64 setId); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 237865424..6ea61e00f 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -1467,6 +1467,8 @@ void StickersListFooter::paintSetIconToCache( return &st().icons.people; } else if (const auto section = SetIdEmojiSection(icon.setId)) { return sectionIcon(*section, selected); + } else if (icon.setId == Data::Stickers::CollectibleSetId) { + return &st().icons.collectibles; } return sectionIcon(Section::Recent, selected); }()); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 4c7efa83a..f0900e98e 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -1017,7 +1017,7 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) { } void TabbedSelector::provideRecentEmoji( - const std::vector &customRecentList) { + const std::vector &customRecentList) { for (const auto &tab : _tabs) { if (tab.type() == SelectorTab::Emoji) { const auto emoji = static_cast(tab.widget()); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index f96ca6b6d..ad8821d84 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -62,6 +62,7 @@ struct FileChosen { not_null document; Api::SendOptions options; Ui::MessageSendingAnimationFrom messageSendingFrom; + std::shared_ptr collectible; TextWithTags caption; }; @@ -154,7 +155,7 @@ public: void refreshStickers(); void setCurrentPeer(PeerData *peer); void provideRecentEmoji( - const std::vector &customRecentList); + const std::vector &customRecentList); void hideFinished(); void showStarted(); diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp index 63ef7e76a..f6d251b99 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -84,6 +84,10 @@ void EmojiStatuses::refreshChannelColored() { requestChannelColored(); } +void EmojiStatuses::refreshCollectibles() { + requestCollectibles(); +} + void EmojiStatuses::refreshRecentDelayed() { if (_recentRequestId || _recentRequestScheduled) { return; @@ -103,6 +107,7 @@ const std::vector &EmojiStatuses::list(Type type) const { case Type::Colored: return _colored; case Type::ChannelDefault: return _channelDefault; case Type::ChannelColored: return _channelColored; + case Type::Collectibles: return _collectibles; } Unexpected("Type in EmojiStatuses::list."); } @@ -371,6 +376,7 @@ void EmojiStatuses::requestColored() { _coloredRequestId = 0; result.match([&](const MTPDmessages_stickerSet &data) { updateColored(data); + refreshCollectibles(); }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); @@ -418,6 +424,25 @@ void EmojiStatuses::requestChannelColored() { }).send(); } +void EmojiStatuses::requestCollectibles() { + if (_collectiblesRequestId) { + return; + } + auto &api = _owner->session().api(); + _collectiblesRequestId = api.request( + MTPaccount_GetCollectibleEmojiStatuses(MTP_long(_collectiblesHash)) + ).done([=](const MTPaccount_EmojiStatuses &result) { + _collectiblesRequestId = 0; + result.match([&](const MTPDaccount_emojiStatuses &data) { + updateCollectibles(data); + }, [&](const MTPDaccount_emojiStatusesNotModified &) { + }); + }).fail([=] { + _collectiblesRequestId = 0; + _collectiblesHash = 0; + }).send(); +} + void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) { _recentHash = data.vhash().v; _recent = parse(data); @@ -462,6 +487,13 @@ void EmojiStatuses::updateChannelColored( _channelColoredUpdated.fire({}); } +void EmojiStatuses::updateCollectibles( + const MTPDaccount_emojiStatuses &data) { + _collectiblesHash = data.vhash().v; + _collectibles = parse(data); + _collectiblesUpdated.fire({}); +} + void EmojiStatuses::set(EmojiStatusId id, TimeId until) { set(_owner->session().user(), id, until); } diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index 203017219..167b908a0 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.h +++ b/Telegram/SourceFiles/data/data_emoji_statuses.h @@ -58,6 +58,7 @@ public: void refreshColored(); void refreshChannelDefault(); void refreshChannelColored(); + void refreshCollectibles(); enum class Type { Recent, @@ -103,12 +104,14 @@ private: void requestColored(); void requestChannelDefault(); void requestChannelColored(); + void requestCollectibles(); void updateRecent(const MTPDaccount_emojiStatuses &data); void updateDefault(const MTPDaccount_emojiStatuses &data); void updateColored(const MTPDmessages_stickerSet &data); void updateChannelDefault(const MTPDaccount_emojiStatuses &data); void updateChannelColored(const MTPDmessages_stickerSet &data); + void updateCollectibles(const MTPDaccount_emojiStatuses &data); void processClearingIn(TimeId wait); void processClearing(); @@ -153,6 +156,7 @@ private: mtpRequestId _channelColoredRequestId = 0; mtpRequestId _collectiblesRequestId = 0; + uint64 _collectiblesHash = 0; base::flat_map, mtpRequestId> _sentRequests; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index 114d41176..97e0014e8 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -814,6 +814,25 @@ void Stickers::setPackAndEmoji( } } +not_null Stickers::collectibleSet() { + const auto setId = CollectibleSetId; + auto &sets = setsRef(); + auto it = sets.find(setId); + if (it == sets.cend()) { + it = sets.emplace(setId, std::make_unique( + &owner(), + setId, + uint64(0), // accessHash + uint64(0), // hash + tr::lng_collectible_emoji(tr::now), + QString(), + 0, // count + SetFlag::Special, + TimeId(0))).first; + } + return it->second.get(); +} + void Stickers::specialSetReceived( uint64 setId, const QString &setTitle, diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.h b/Telegram/SourceFiles/data/stickers/data_stickers.h index 426b034aa..affff47b8 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers.h @@ -65,6 +65,9 @@ public: // For setting up megagroup sticker set. static constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL; + // For collectible emoji statuses. + static constexpr auto CollectibleSetId = 0xFFFFFFFFFFFFFFF8ULL; + void notifyUpdated(StickersType type); [[nodiscard]] rpl::producer updated() const; [[nodiscard]] rpl::producer<> updated(StickersType type) const; @@ -244,6 +247,8 @@ public: [[nodiscard]] auto getEmojiListFromSet(not_null document) -> std::optional>>; + [[nodiscard]] not_null collectibleSet(); + not_null feedSet(const MTPStickerSet &data); not_null feedSet(const MTPStickerSetCovered &data); not_null feedSetFull(const MTPDmessages_stickerSet &data); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 52d6a1a09..d23f3d1b7 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -33,6 +33,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_chat_filters.h" #include "data/data_send_action.h" +#include "data/data_star_gift.h" +#include "data/data_emoji_statuses.h" #include "data/data_folder.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" @@ -1300,6 +1302,15 @@ void History::newItemAdded(not_null item) { if (const auto topic = item->topic()) { topic->applyItemAdded(item); } + if (const auto media = item->media()) { + if (const auto gift = media->gift()) { + if (const auto unique = gift->unique.get()) { + if (unique->ownerId == session().userPeerId()) { + owner().emojiStatuses().refreshCollectibles(); + } + } + } + } } void History::registerClientSideMessage(not_null item) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index dd23ee629..db17b49ca 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -522,9 +522,7 @@ auto UniqueGiftBg( p.setBrush(Qt::transparent); p.drawRoundedRect(inner, radius, radius); } - auto gradient = QRadialGradient( - inner.center(), - inner.height() / 2); + auto gradient = QRadialGradient(inner.center(), inner.height() / 2); gradient.setStops({ { 0., gift->backdrop.centerColor }, { 1., gift->backdrop.edgeColor }, diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 32eb1a1f4..38970fb09 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -1047,7 +1047,7 @@ void Selector::createList() { .show = _show, .mode = _listMode, .paused = _paused ? _paused : [] { return false; }, - .customRecentList = std::move(recentList), + .customRecentList = DocumentListToRecent(recentList), .customRecentFactory = _unifiedFactoryOwner->factory(), .freeEffects = std::move(freeEffects), .st = st, diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp index 5663312f6..ecf24b279 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp @@ -70,7 +70,7 @@ EmojiStatusPanel::~EmojiStatusPanel() { } } -void EmojiStatusPanel::setChooseFilter(Fn filter) { +void EmojiStatusPanel::setChooseFilter(Fn filter) { _chooseFilter = std::move(filter); } @@ -83,6 +83,7 @@ void EmojiStatusPanel::show( .button = button, .animationSizeTag = animationSizeTag, .ensureAddedEmojiId = controller->session().user()->emojiStatusId(), + .withCollectibles = true, }); } @@ -116,20 +117,14 @@ void EmojiStatusPanel::show(Descriptor &&descriptor) { if (now && !ranges::contains(list, now)) { list.push_back(now); } - auto tmp = std::vector(); - for (const auto &id : list) { - if (id.documentId) { // todo collectibles - tmp.push_back(id.documentId); - } - } - _panel->selector()->provideRecentEmoji(tmp); + _panel->selector()->provideRecentEmoji(list); }; if (descriptor.backgroundEmojiMode) { controller->session().api().peerPhoto().emojiListValue( Api::PeerPhoto::EmojiListType::Background ) | rpl::start_with_next([=](std::vector &&list) { auto tmp = std::vector(); - for (const auto &id : list) { // todo collectibles + for (const auto &id : list) { tmp.push_back(EmojiStatusId{ .documentId = id }); } feed(std::move(tmp)); @@ -203,6 +198,8 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { using Mode = ChatHelpers::TabbedSelector::Mode; const auto controller = descriptor.controller; const auto body = controller->window().widget()->bodyWidget(); + auto features = ChatHelpers::ComposeFeatures(); + features.collectibleStatus = descriptor.withCollectibles; _panel = base::make_unique_q( body, controller, @@ -221,6 +218,7 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { ? Mode::ChannelStatus : Mode::EmojiStatus), .customTextColor = descriptor.customTextColor, + .features = features, })); _customTextColor = descriptor.customTextColor; _backgroundEmojiMode = descriptor.backgroundEmojiMode; @@ -233,7 +231,7 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { _panel->hide(); struct Chosen { - DocumentId id = 0; + EmojiStatusId id; TimeId until = 0; Ui::MessageSendingAnimationFrom animation; }; @@ -246,7 +244,10 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { auto statusChosen = _panel->selector()->customEmojiChosen( ) | rpl::map([=](ChatHelpers::FileChosen data) { return Chosen{ - .id = data.document->id, + .id = { + data.collectible ? DocumentId() : data.document->id, + data.collectible, + }, .until = data.options.scheduled, .animation = data.messageSendingFrom, }; @@ -264,8 +265,8 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { ) | rpl::start_with_next([=](const Chosen &chosen) { const auto owner = &controller->session().data(); startAnimation(owner, body, chosen.id, chosen.animation); - _someCustomChosen.fire({ { chosen.id }, chosen.until }); - _panel->hideAnimated(); // todo collectibles + _someCustomChosen.fire({ chosen.id, chosen.until }); + _panel->hideAnimated(); }, _panel->lifetime()); } else { const auto weak = Ui::MakeWeak(_panel.get()); @@ -276,8 +277,8 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { const auto owner = &controller->session().data(); if (weak) { startAnimation(owner, body, chosen.id, chosen.animation); - } // todo collectibles - owner->emojiStatuses().set({ chosen.id }, chosen.until); + } + owner->emojiStatuses().set(chosen.id, chosen.until); }; rpl::merge( @@ -301,7 +302,7 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { bool EmojiStatusPanel::filter( not_null controller, - DocumentId chosenId) const { + EmojiStatusId chosenId) const { if (_chooseFilter) { return _chooseFilter(chosenId); } else if (chosenId && !controller->session().premium()) { @@ -314,13 +315,16 @@ bool EmojiStatusPanel::filter( void EmojiStatusPanel::startAnimation( not_null owner, not_null body, - DocumentId statusId, + EmojiStatusId statusId, Ui::MessageSendingAnimationFrom from) { if (!_panelButton || !statusId) { return; } + const auto documentId = statusId.collectible + ? statusId.collectible->documentId + : statusId.documentId; auto args = Ui::ReactionFlyAnimationArgs{ - .id = { { statusId } }, + .id = { { documentId } }, .flyIcon = from.frame, .flyFrom = body->mapFromGlobal(from.globalStartGeometry), .forceFirstFrame = _backgroundEmojiMode, diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h index 05f5d0ea7..16040e025 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h @@ -40,7 +40,7 @@ public: EmojiStatusPanel(); ~EmojiStatusPanel(); - void setChooseFilter(Fn filter); + void setChooseFilter(Fn filter); void show( not_null controller, @@ -56,6 +56,7 @@ public: Fn customTextColor; bool backgroundEmojiMode = false; bool channelStatusMode = false; + bool withCollectibles = false; }; void show(Descriptor &&descriptor); void repaint(); @@ -74,17 +75,17 @@ private: void create(const Descriptor &descriptor); [[nodiscard]] bool filter( not_null controller, - DocumentId chosenId) const; + EmojiStatusId chosenId) const; void startAnimation( not_null owner, not_null body, - DocumentId statusId, + EmojiStatusId statusId, Ui::MessageSendingAnimationFrom from); base::unique_qptr _panel; Fn _customTextColor; - Fn _chooseFilter; + Fn _chooseFilter; QPointer _panelButton; std::unique_ptr _animation; rpl::event_stream _someCustomChosen; diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp index 7d4f075cc..5f77c5512 100644 --- a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp +++ b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_widget.cpp @@ -243,7 +243,7 @@ EmojiSelector::Selector EmojiSelector::createEmojiList( .show = _controller->uiShow(), .mode = ChatHelpers::EmojiListMode::UserpicBuilder, .paused = [=] { return true; }, - .customRecentList = _lastRecent, + .customRecentList = ChatHelpers::DocumentListToRecent(_lastRecent), .customRecentFactory = [=](DocumentId id, Fn repaint) { return manager->create(id, std::move(repaint), tag); }, diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index e6b41da6f..717788c29 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -618,6 +618,7 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) { } icons: ComposeIcons { settings: icon {{ "emoji/emoji_settings", storiesComposeGrayIcon }}; + collectibles: icon {{ "menu/unique", storiesComposeGrayIcon }}; recent: icon {{ "emoji/emoji_recent", storiesComposeGrayIcon }}; recentActive: icon {{ "emoji/emoji_recent", storiesComposeWhiteText }}; diff --git a/Telegram/SourceFiles/storage/serialize_document.cpp b/Telegram/SourceFiles/storage/serialize_document.cpp index e13a587cf..89b335ce4 100644 --- a/Telegram/SourceFiles/storage/serialize_document.cpp +++ b/Telegram/SourceFiles/storage/serialize_document.cpp @@ -137,7 +137,8 @@ DocumentData *Document::readFromStreamHelper( || info->setId == Data::Stickers::CloudRecentSetId || info->setId == Data::Stickers::CloudRecentAttachedSetId || info->setId == Data::Stickers::FavedSetId - || info->setId == Data::Stickers::CustomSetId) { + || info->setId == Data::Stickers::CustomSetId + || info->setId == Data::Stickers::CollectibleSetId) { typeOfSet = StickerSetTypeEmpty; } diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 3c8d81403..afa677ed4 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -2193,7 +2193,8 @@ void Account::writeInstalledStickers() { writeStickerSets(_installedStickersKey, [](const Data::StickersSet &set) { if (set.id == Data::Stickers::CloudRecentSetId || set.id == Data::Stickers::FavedSetId - || set.id == Data::Stickers::CloudRecentAttachedSetId) { + || set.id == Data::Stickers::CloudRecentAttachedSetId + || set.id == Data::Stickers::CollectibleSetId) { // separate files for them return StickerSetCheckResult::Skip; } else if (set.flags & SetFlag::Special) { @@ -2220,7 +2221,8 @@ void Account::writeFeaturedStickers() { writeStickerSets(_featuredStickersKey, [](const Data::StickersSet &set) { if (set.id == Data::Stickers::CloudRecentSetId || set.id == Data::Stickers::FavedSetId - || set.id == Data::Stickers::CloudRecentAttachedSetId) { + || set.id == Data::Stickers::CloudRecentAttachedSetId + || set.id == Data::Stickers::CollectibleSetId) { // separate files for them return StickerSetCheckResult::Skip; } else if ((set.flags & SetFlag::Special)