diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 84039f797..2a950f23c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1821,7 +1821,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stickers_count#other" = "{count} stickers"; "lng_masks_count#one" = "{count} mask"; "lng_masks_count#other" = "{count} masks"; +"lng_custom_emoji_count#one" = "{count} emoji"; +"lng_custom_emoji_count#other" = "{count} emoji"; "lng_stickers_attached_sets" = "Sets of attached stickers"; +"lng_custom_emoji_used_sets" = "Sets of used emoji"; "lng_stickers_group_set" = "Group sticker set"; "lng_stickers_remove_group_set" = "Remove group sticker set?"; "lng_stickers_group_from_your" = "Choose from your stickers"; @@ -2147,8 +2150,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_delete_all_files" = "Delete all files"; "lng_context_save_custom_sound" = "Save for notifications"; -"lng_context_animated_emoji" = "This message contains emoji from {name} pack."; -"lng_context_animated_emoji_many" = "This message contains emoji from {name} packs."; +"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**."; +"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**."; +"lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**."; "lng_downloads_section" = "Downloads"; "lng_downloads_view_in_chat" = "View in chat"; diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index ffa3ba15d..107bf7419 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -152,6 +152,7 @@ private: bool isRecentSet() const; bool isMasksSet() const; + bool isEmojiSet() const; bool isWebm() const; bool isInstalled() const; bool isUnread() const; @@ -419,32 +420,56 @@ StickersBox::StickersBox( , _section(Section::Attached) , _isMasks(false) , _attached(0, this, controller, Section::Attached) +, _attachedType(Data::StickersType::Stickers) , _attachedSets(attachedSets) { } +StickersBox::StickersBox( + QWidget*, + not_null controller, + const std::vector &emojiSets) +: _controller(controller) +, _api(&controller->session().mtp()) +, _section(Section::Attached) +, _isMasks(false) +, _attached(0, this, controller, Section::Attached) +, _attachedType(Data::StickersType::Emoji) +, _emojiSets(emojiSets) { +} + Main::Session &StickersBox::session() const { return _controller->session(); } void StickersBox::showAttachedStickers() { + const auto stickers = &session().data().stickers(); + auto addedSet = false; + const auto add = [&](not_null set) { + if (_attached.widget()->appendSet(set)) { + addedSet = true; + if (set->stickers.isEmpty() + || (set->flags & SetFlag::NotLoaded)) { + session().api().scheduleStickerSetRequest( + set->id, + set->accessHash); + } + } + }; for (const auto &stickerSet : _attachedSets.v) { const auto setData = stickerSet.match([&](const auto &data) { return data.vset().match([&](const MTPDstickerSet &data) { return &data; }); }); - - if (const auto set = session().data().stickers().feedSet(*setData)) { - if (_attached.widget()->appendSet(set)) { - addedSet = true; - if (set->stickers.isEmpty() - || (set->flags & SetFlag::NotLoaded)) { - session().api().scheduleStickerSetRequest( - set->id, - set->accessHash); - } - } + if (const auto set = stickers->feedSet(*setData)) { + add(set); + } + } + for (const auto &setId : _emojiSets) { + const auto i = stickers->sets().find(setId.id); + if (i != end(stickers->sets())) { + add(i->second.get()); } } if (addedSet) { @@ -547,7 +572,9 @@ void StickersBox::prepare() { } else if (_section == Section::Archived) { requestArchivedSets(); } else if (_section == Section::Attached) { - setTitle(tr::lng_stickers_attached_sets()); + setTitle(_attachedType == Data::StickersType::Emoji + ? tr::lng_custom_emoji_used_sets() + : tr::lng_stickers_attached_sets()); } if (_tabs) { if (archivedSetsOrder().isEmpty()) { @@ -1063,6 +1090,10 @@ bool StickersBox::Inner::Row::isMasksSet() const { return (set->flags & SetFlag::Masks); } +bool StickersBox::Inner::Row::isEmojiSet() const { + return (set->flags & SetFlag::Emoji); +} + bool StickersBox::Inner::Row::isWebm() const { return (set->flags & SetFlag::Webm); } @@ -1331,6 +1362,8 @@ void StickersBox::Inner::paintRow(Painter &p, not_null row, int index) { const auto statusText = (row->count == 0) ? tr::lng_contacts_loading(tr::now) + : row->isEmojiSet() + ? tr::lng_custom_emoji_count(tr::now, lt_count, row->count) : row->isMasksSet() ? tr::lng_masks_count(tr::now, lt_count, row->count) : tr::lng_stickers_count(tr::now, lt_count, row->count); diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 9f233b358..8ee280719 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -37,6 +37,7 @@ class Session; namespace Data { class DocumentMedia; +enum class StickersType : uchar; } // namespace Data namespace Lottie { @@ -70,6 +71,10 @@ public: QWidget*, not_null controller, const MTPVector &attachedSets); + StickersBox( + QWidget*, + not_null controller, + const std::vector &emojiSets); ~StickersBox(); [[nodiscard]] Main::Session &session() const; @@ -157,7 +162,9 @@ private: Tab _attached; Tab *_tab = nullptr; + const Data::StickersType _attachedType = {}; const MTPVector _attachedSets; + const std::vector _emojiSets; ChannelData *_megagroupSet = nullptr; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index ab01b7ada..060ec770d 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -415,6 +415,12 @@ std::unique_ptr CustomEmojiManager::createLoader( return result; } +QString CustomEmojiManager::lookupSetName(uint64 setId) { + const auto &sets = _owner->stickers().sets(); + const auto i = sets.find(setId); + return (i != end(sets)) ? i->second->title : QString(); +} + void CustomEmojiManager::request() { auto ids = QVector(); ids.reserve(std::min(kMaxPerRequest, int(_pendingForRequest.size()))); @@ -440,6 +446,7 @@ void CustomEmojiManager::request() { } } } + requestSetFor(document); } requestFinished(); }).fail([=] { @@ -448,6 +455,30 @@ void CustomEmojiManager::request() { }).send(); } +void CustomEmojiManager::requestSetFor(not_null document) { + const auto sticker = document->sticker(); + if (!sticker || !sticker->set.id) { + return; + } + const auto &sets = document->owner().stickers().sets(); + const auto i = sets.find(sticker->set.id); + if (i != end(sets)) { + return; + } + const auto session = &document->session(); + session->api().scheduleStickerSetRequest( + sticker->set.id, + sticker->set.accessHash); + if (_requestSetsScheduled) { + return; + } + _requestSetsScheduled = true; + crl::on_main(this, [=] { + _requestSetsScheduled = false; + session->api().requestStickerSets(); + }); +} + void CustomEmojiManager::requestFinished() { _requestId = 0; if (!_pendingForRequest.empty()) { diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 357eae4ac..160c2be87 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -49,6 +49,8 @@ public: DocumentId documentId, SizeTag tag); + [[nodiscard]] QString lookupSetName(uint64 setId); + [[nodiscard]] Main::Session &session() const; [[nodiscard]] Session &owner() const; @@ -65,6 +67,7 @@ private: Ui::CustomEmoji::RepaintRequest request); void scheduleRepaintTimer(); void invokeRepaints(); + void requestSetFor(not_null document); const not_null _owner; @@ -81,6 +84,7 @@ private: crl::time _repaintNext = 0; base::Timer _repaintTimer; bool _repaintTimerScheduled = false; + bool _requestSetsScheduled = false; }; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index 2bfae4b58..35429b365 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -1410,13 +1410,18 @@ StickersSet *Stickers::feedSetFull(const MTPDmessages_stickerSet &d) { } } + const auto isEmoji = !!(set->flags & SetFlag::Emoji); const auto isMasks = !!(set->flags & SetFlag::Masks); if (pack.isEmpty()) { - const auto removeIndex = (isMasks + const auto removeIndex = (isEmoji + ? emojiSetsOrder() + : isMasks ? maskSetsOrder() : setsOrder()).indexOf(set->id); if (removeIndex >= 0) { - (isMasks + (isEmoji + ? emojiSetsOrderRef() + : isMasks ? maskSetsOrderRef() : setsOrderRef()).removeAt(removeIndex); } @@ -1453,10 +1458,12 @@ StickersSet *Stickers::feedSetFull(const MTPDmessages_stickerSet &d) { if (set) { const auto isArchived = !!(set->flags & SetFlag::Archived); - if (isMasks) { - session().local().writeInstalledMasks(); - } else if (set->flags & SetFlag::Installed) { - if (!isArchived) { + if ((set->flags & SetFlag::Installed) && !isArchived) { + if (isEmoji) { + session().local().writeInstalledCustomEmoji(); + } else if (isMasks) { + session().local().writeInstalledMasks(); + } else { session().local().writeInstalledStickers(); } } @@ -1464,7 +1471,9 @@ StickersSet *Stickers::feedSetFull(const MTPDmessages_stickerSet &d) { session().local().writeFeaturedStickers(); } if (wasArchived != isArchived) { - if (isMasks) { + if (isEmoji) { + + } else if (isMasks) { session().local().writeArchivedMasks(); } else { session().local().writeArchivedStickers(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 005768aa4..de3fbb7f5 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2412,6 +2412,16 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } + auto emojiPackIds = _dragStateItem + ? HistoryView::CollectEmojiPacks(_dragStateItem) + : std::vector(); + if (!emojiPackIds.empty()) { + HistoryView::AddEmojiPacksAction( + _menu, + this, + std::move(emojiPackIds), + _controller); + } if (hasWhoReactedItem) { HistoryView::AddWhoReactedAction( _menu, diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 1c5c437c8..cefe4b233 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -26,8 +26,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_web_page.h" #include "history/view/reactions/message_reactions_list.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/menu/menu_item_base.h" #include "ui/image/image.h" #include "ui/toast/toast.h" +#include "ui/text/text_utilities.h" #include "ui/controls/delete_message_context_action.h" #include "ui/controls/who_reacted_context_action.h" #include "ui/boxes/report_box.h" @@ -37,6 +39,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/stickers_box.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_document.h" @@ -48,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_scheduled_messages.h" #include "data/data_message_reactions.h" +#include "data/stickers/data_custom_emoji.h" #include "core/file_utilities.h" #include "core/click_handler_types.h" #include "base/platform/base_platform_info.h" @@ -1210,4 +1214,121 @@ void ShowWhoReactedMenu( }, lifetime); } +std::vector CollectEmojiPacks( + not_null item) { + auto result = std::vector(); + const auto owner = &item->history()->owner(); + for (const auto &entity : item->originalText().entities) { + if (entity.type() == EntityType::CustomEmoji) { + const auto data = Data::ParseCustomEmojiData(entity.data()); + if (const auto set = owner->document(data.id)->sticker()) { + if (set->set.id + && !ranges::contains( + result, + set->set.id, + &StickerSetIdentifier::id)) { + result.push_back(set->set); + } + } + } + } + return result; +} + +void AddEmojiPacksAction( + not_null menu, + not_null context, + std::vector packIds, + not_null controller) { + class Item final : public Ui::Menu::ItemBase { + public: + Item( + not_null parent, + const style::Menu &st, + TextWithEntities &&about) + : Ui::Menu::ItemBase(parent, st) + , _st(st) + , _text(base::make_unique_q( + this, + rpl::single(std::move(about)), + st::historyHasCustomEmoji)) + , _dummyAction(new QAction(parent)) { + enableMouseSelecting(); + _text->setAttribute(Qt::WA_TransparentForMouseEvents); + parent->widthValue() | rpl::start_with_next([=](int width) { + const auto top = st::historyHasCustomEmojiPosition.y(); + const auto skip = st::historyHasCustomEmojiPosition.x(); + _text->resizeToWidth(width - 2 * skip); + _text->moveToLeft(skip, top); + resize(width, contentHeight()); + }, lifetime()); + } + + not_null action() const override { + return _dummyAction; + } + + bool isEnabled() const override { + return true; + } + + private: + int contentHeight() const override { + const auto skip = st::historyHasCustomEmojiPosition.y(); + return skip + _text->height() + skip; + } + + void paintEvent(QPaintEvent *e) override { + auto p = QPainter(this); + const auto selected = isSelected(); + p.fillRect(rect(), selected ? _st.itemBgOver : _st.itemBg); + RippleButton::paintRipple(p, 0, 0); + } + + const style::Menu &_st; + const base::unique_qptr _text; + const not_null _dummyAction; + + }; + + const auto count = int(packIds.size()); + const auto manager = &controller->session().data().customEmojiManager(); + const auto name = (count == 1) + ? TextWithEntities{ manager->lookupSetName(packIds[0].id) } + : TextWithEntities(); + if (!menu->empty()) { + menu->addSeparator(); + } + auto button = base::make_unique_q( + menu, + menu->st().menu, + (name.text.isEmpty() + ? tr::lng_context_animated_emoji_many( + tr::now, + lt_count, + count, + Ui::Text::RichLangValue) + : tr::lng_context_animated_emoji( + tr::now, + lt_name, + TextWithEntities{ name }, + Ui::Text::RichLangValue))); + const auto weak = base::make_weak(controller.get()); + button->setClickedCallback([=] { + const auto strong = weak.get(); + if (!strong) { + return; + } else if (packIds.size() > 1) { + strong->show(Box(strong, packIds)); + return; + } + // Single used emoji pack. + strong->show( + Box(strong, packIds.front()), + Ui::LayerOption::KeepOther); + + }); + menu->addAction(std::move(button)); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index 79e52c4ca..d7d8ac773 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -79,4 +79,12 @@ void ShowWhoReactedMenu( not_null controller, rpl::lifetime &lifetime); +[[nodiscard]] std::vector CollectEmojiPacks( + not_null item); +void AddEmojiPacksAction( + not_null menu, + not_null context, + std::vector packIds, + not_null controller); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index bf24da1f6..f676a7573 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -2128,7 +2128,14 @@ void Account::writeInstalledCustomEmoji() { using SetFlag = Data::StickersSetFlag; writeStickerSets(_installedCustomEmojiKey, [](const Data::StickersSet &set) { - if (!(set.flags & SetFlag::Emoji) || set.stickers.isEmpty()) { + if (!(set.flags & SetFlag::Emoji)) { + return StickerSetCheckResult::Skip; + } else if (set.flags & SetFlag::NotLoaded) { + // waiting to receive + return StickerSetCheckResult::Abort; + } else if (!(set.flags & SetFlag::Installed) + || (set.flags & SetFlag::Archived) + || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index d48a8f894..ce0163aea 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1087,3 +1087,13 @@ msgServiceGiftBoxButtonPadding: margins(0px, 17px, 0px, 17px); msgServiceGiftBoxTitlePadding: margins(0px, 126px, 0px, 2px); msgServiceGiftBoxStickerTop: -30px; msgServiceGiftBoxStickerSize: size(150px, 150px); + +historyHasCustomEmoji: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: font(12px); + linkFont: font(12px underline); + linkFontOver: font(12px underline); + } + minWidth: 80px; +} +historyHasCustomEmojiPosition: point(12px, 4px);