From 7b22a2aea7980e0e4161ae3253eef4fbfb1f9332 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 20 Apr 2026 14:28:26 +0300 Subject: [PATCH] Added Add-to-Sticker-Set submenu for stickers from foreign packs. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/api/api_stickers_creator.cpp | 137 ++++++++++++++++++ .../SourceFiles/api/api_stickers_creator.h | 28 ++++ .../SourceFiles/boxes/sticker_set_box.cpp | 15 +- .../history/history_inner_widget.cpp | 6 + .../view/history_view_context_menu.cpp | 8 + .../SourceFiles/ui/dynamic_thumbnails.cpp | 10 ++ Telegram/SourceFiles/ui/dynamic_thumbnails.h | 3 + 8 files changed, 203 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ae7828b7fe..2bcb2a3b06 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4514,6 +4514,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stickers_create_new" = "Create a New Sticker"; "lng_stickers_add_existing" = "Add an Existing Sticker"; +"lng_stickers_add_to_set" = "Add to Sticker Set"; +"lng_stickers_already_in_set" = "This Sticker is already in the Set."; +"lng_stickers_set_is_full" = "This Sticker Set is full."; "lng_stickers_pack_choose_emoji_title" = "Choose Emoji"; "lng_stickers_pack_choose_emoji_about" = "Pick an emoji that corresponds to this sticker."; "lng_stickers_pick_existing_title" = "Choose Sticker"; diff --git a/Telegram/SourceFiles/api/api_stickers_creator.cpp b/Telegram/SourceFiles/api/api_stickers_creator.cpp index 405719e2c4..d9d86d9301 100644 --- a/Telegram/SourceFiles/api/api_stickers_creator.cpp +++ b/Telegram/SourceFiles/api/api_stickers_creator.cpp @@ -10,13 +10,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/random.h" #include "base/unixtime.h" +#include "chat_helpers/compose/compose_show.h" #include "data/data_document.h" +#include "data/data_file_origin.h" #include "data/data_session.h" #include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers_set.h" +#include "lang/lang_keys.h" #include "main/main_session.h" +#include "menu/menu_action_with_thumbnail.h" #include "storage/file_upload.h" #include "storage/localimageloader.h" +#include "styles/style_menu_icons.h" +#include "styles/style_widgets.h" +#include "ui/dynamic_thumbnails.h" +#include "ui/widgets/menu/menu_add_action_callback.h" +#include "ui/widgets/menu/menu_common.h" +#include "ui/widgets/popup_menu.h" namespace Api { namespace { @@ -81,6 +91,35 @@ void FeedSetIfFull( }); } +template +void EnumerateOwnedStickerSets( + not_null session, + Callback &&callback) { + const auto &stickers = session->data().stickers(); + const auto &sets = stickers.sets(); + for (const auto setId : stickers.setsOrder()) { + const auto it = sets.find(setId); + if (it == sets.end()) { + continue; + } + const auto set = it->second.get(); + if (!(set->flags & Data::StickersSetFlag::AmCreator) + || (set->type() != Data::StickersType::Stickers)) { + continue; + } + using namespace Data; + if constexpr (std::is_same_v< + bool, + std::invoke_result_t>>) { + if (!callback(set)) { + return; + } + } else { + callback(set); + } + } +} + } // namespace void AddExistingStickerToSet( @@ -105,6 +144,104 @@ void AddExistingStickerToSet( }).handleFloodErrors().send(); } +QString StickerEmojiOrDefault(not_null document) { + if (const auto sticker = document->sticker()) { + if (!sticker->alt.isEmpty()) { + return sticker->alt; + } + } + return QString::fromUtf8("\xF0\x9F\x99\x82"); +} + +bool HasOwnedStickerSets(not_null session) { + auto found = false; + EnumerateOwnedStickerSets(session, [&](not_null) { + found = true; + return false; + }); + return found; +} + +void FillChooseStickerSetMenu( + not_null menu, + std::shared_ptr show, + not_null document) { + const auto session = &show->session(); + const auto emoji = StickerEmojiOrDefault(document); + const auto failToast = [=](QString err) { + show->showToast(err.isEmpty() + ? tr::lng_attach_failed(tr::now) + : err); + }; + EnumerateOwnedStickerSets(session, [&](not_null set) { + const auto identifier = set->identifier(); + const auto coverDocument = set->lookupThumbnailDocument(); + auto thumbnail = coverDocument + ? Ui::MakeDocumentThumbnail( + coverDocument, + Data::FileOriginStickerSet(set->id, set->accessHash)) + : nullptr; + const auto targetSetId = set->id; + const auto handler = crl::guard(session, [=] { + const auto &map = session->data().stickers().sets(); + const auto i = map.find(targetSetId); + if (i != map.end() + && i->second->count >= kStickersInOwnedSetMax) { + show->showToast(tr::lng_stickers_set_is_full(tr::now)); + return; + } + const auto oldCount = (i != map.end()) + ? i->second->count + : 0; + AddExistingStickerToSet( + session, + identifier, + document, + emoji, + crl::guard(session, [=](MTPmessages_StickerSet) { + const auto &map = session->data().stickers().sets(); + const auto i = map.find(targetSetId); + const auto newCount = (i != map.end()) + ? i->second->count + : oldCount; + show->showToast(newCount > oldCount + ? tr::lng_stickers_create_added(tr::now) + : tr::lng_stickers_already_in_set(tr::now)); + }), + crl::guard(session, failToast)); + }); + const auto rawAction = Ui::Menu::CreateAction( + menu.get(), + set->title, + handler); + auto item = base::make_unique_q( + menu->menu(), + menu->menu()->st(), + rawAction, + std::move(thumbnail), + st::menuIconStickerAdd.width()); + menu->addAction(std::move(item)); + }); +} + +void AddAddToStickerSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document) { + const auto session = &show->session(); + if (!HasOwnedStickerSets(session)) { + return; + } + addAction({ + .text = tr::lng_stickers_add_to_set(tr::now), + .icon = &st::menuIconStickerAdd, + .fillSubmenu = [show, document](not_null submenu) { + FillChooseStickerSetMenu(submenu, show, document); + }, + .submenuSt = &st::popupMenuWithIcons, + }); +} + void DeleteStickerSet( not_null session, const StickerSetIdentifier &set, diff --git a/Telegram/SourceFiles/api/api_stickers_creator.h b/Telegram/SourceFiles/api/api_stickers_creator.h index 390fe72e5a..a97affca18 100644 --- a/Telegram/SourceFiles/api/api_stickers_creator.h +++ b/Telegram/SourceFiles/api/api_stickers_creator.h @@ -13,12 +13,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class DocumentData; +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Main { class Session; } // namespace Main +namespace Ui { +class PopupMenu; +namespace Menu { +struct MenuCallback; +} // namespace Menu +} // namespace Ui + namespace Api { +inline constexpr auto kStickersInOwnedSetMax = 120; + void AddExistingStickerToSet( not_null session, const StickerSetIdentifier &set, @@ -33,6 +46,21 @@ void DeleteStickerSet( Fn done, Fn fail); +[[nodiscard]] bool HasOwnedStickerSets(not_null session); + +[[nodiscard]] QString StickerEmojiOrDefault( + not_null document); + +void FillChooseStickerSetMenu( + not_null menu, + std::shared_ptr show, + not_null document); + +void AddAddToStickerSetAction( + const Ui::Menu::MenuCallback &addAction, + std::shared_ptr show, + not_null document); + class StickerUpload final : public base::has_weak_ptr { public: StickerUpload( diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 61d45c4ce2..eabf4b93d5 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -85,7 +85,6 @@ constexpr auto kMinRepaintDelay = crl::time(33); constexpr auto kMinAfterScrollDelay = crl::time(33); constexpr auto kGrayLockOpacity = 0.3; constexpr auto kStickerMoveDuration = crl::time(200); -constexpr auto kOwnedSetStickersMax = 120; using Data::StickersSet; using Data::StickersPack; @@ -1638,6 +1637,12 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) { (isFaved ? &st::menuIconUnfave : &st::menuIconFave)); + if (!amSetCreator()) { + Api::AddAddToStickerSetAction( + Ui::Menu::CreateAddActionCallback(_menu.get()), + _show, + document); + } if (amSetCreator()) { const auto addAction = Ui::Menu::CreateAddActionCallback( _menu.get()); @@ -2367,7 +2372,7 @@ bool StickerSetBox::Inner::hasAddCell() const { && _amSetCreator && (setType() == Data::StickersType::Stickers) && !_pack.isEmpty() - && (_pack.size() < kOwnedSetStickersMax); + && (_pack.size() < Api::kStickersInOwnedSetMax); } int StickerSetBox::Inner::totalCellsCount() const { @@ -2504,11 +2509,7 @@ void StickerSetBox::Inner::startAddExistingStickerFlow() { if (_pickerPanel) { _pickerPanel->hideAnimated(); } - const auto sticker = document->sticker(); - const auto fallback = QString::fromUtf8("\xF0\x9F\x99\x82"); - const auto emoji = (sticker && !sticker->alt.isEmpty()) - ? sticker->alt - : fallback; + const auto emoji = Api::StickerEmojiOrDefault(document); Api::AddExistingStickerToSet( session, identifier, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 022e624c60..599199c81f 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -94,6 +94,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_attached_stickers.h" #include "api/api_suggest_post.h" +#include "api/api_stickers_creator.h" #include "api/api_toggling_media.h" #include "api/api_who_reacted.h" #include "api/api_views.h" @@ -3184,6 +3185,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] { showStickerPackInfo(document); }, &st::menuIconStickers); + } else { + Api::AddAddToStickerSetAction( + Ui::Menu::CreateAddActionCallback(_menu), + _controller->uiShow(), + document); } { const auto isFaved = session->data().stickers().isFaved(document); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index aeb9794c07..58f010229a 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_ringtones.h" #include "api/api_transcribes.h" #include "api/api_who_reacted.h" +#include "api/api_stickers_creator.h" #include "api/api_toggling_media.h" // Api::ToggleFavedSticker #include "base/qt/qt_key_modifiers.h" #include "base/unixtime.h" @@ -33,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_widget.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/menu/menu_action.h" +#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_common.h" #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/menu/menu_separator.h" @@ -290,6 +292,12 @@ void AddDocumentActions( [=] { ShowStickerPackInfo(document, list); }, &st::menuIconStickers); } + if (document->sticker() && !document->sticker()->set) { + Api::AddAddToStickerSetAction( + Ui::Menu::CreateAddActionCallback(menu), + controller->uiShow(), + document); + } if (document->sticker()) { const auto isFaved = document->owner().stickers().isFaved(document); menu->addAction( diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index b6a6514ead..b8c6af32ab 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -1121,6 +1121,16 @@ std::shared_ptr MakeDocumentThumbnail( false); } +std::shared_ptr MakeDocumentThumbnail( + not_null document, + Data::FileOrigin origin) { + return std::make_shared( + document, + origin, + false, + false); +} + std::shared_ptr MakeDocumentThumbnailCenterCrop( not_null document, FullMsgId fullId) { diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h index 2cad87b189..7a16058935 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -51,6 +51,9 @@ class DynamicImage; [[nodiscard]] std::shared_ptr MakeDocumentThumbnail( not_null document, FullMsgId fullId); +[[nodiscard]] std::shared_ptr MakeDocumentThumbnail( + not_null document, + Data::FileOrigin origin); [[nodiscard]] std::shared_ptr MakeDocumentThumbnailCenterCrop( not_null document, FullMsgId fullId);