Added Add-to-Sticker-Set submenu for stickers from foreign packs.

This commit is contained in:
23rd
2026-04-20 14:28:26 +03:00
parent 4b26971b48
commit 7b22a2aea7
8 changed files with 203 additions and 7 deletions
+3
View File
@@ -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";
@@ -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 <typename Callback>
void EnumerateOwnedStickerSets(
not_null<Main::Session*> 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<Callback, not_null<StickersSet*>>>) {
if (!callback(set)) {
return;
}
} else {
callback(set);
}
}
}
} // namespace
void AddExistingStickerToSet(
@@ -105,6 +144,104 @@ void AddExistingStickerToSet(
}).handleFloodErrors().send();
}
QString StickerEmojiOrDefault(not_null<DocumentData*> 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<Main::Session*> session) {
auto found = false;
EnumerateOwnedStickerSets(session, [&](not_null<Data::StickersSet*>) {
found = true;
return false;
});
return found;
}
void FillChooseStickerSetMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> 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<Data::StickersSet*> 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::ActionWithThumbnail>(
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<ChatHelpers::Show> show,
not_null<DocumentData*> 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<Ui::PopupMenu*> submenu) {
FillChooseStickerSetMenu(submenu, show, document);
},
.submenuSt = &st::popupMenuWithIcons,
});
}
void DeleteStickerSet(
not_null<Main::Session*> session,
const StickerSetIdentifier &set,
@@ -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<Main::Session*> session,
const StickerSetIdentifier &set,
@@ -33,6 +46,21 @@ void DeleteStickerSet(
Fn<void()> done,
Fn<void(QString)> fail);
[[nodiscard]] bool HasOwnedStickerSets(not_null<Main::Session*> session);
[[nodiscard]] QString StickerEmojiOrDefault(
not_null<DocumentData*> document);
void FillChooseStickerSetMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document);
void AddAddToStickerSetAction(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
not_null<DocumentData*> document);
class StickerUpload final : public base::has_weak_ptr {
public:
StickerUpload(
@@ -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,
@@ -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);
@@ -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(
@@ -1121,6 +1121,16 @@ std::shared_ptr<DynamicImage> MakeDocumentThumbnail(
false);
}
std::shared_ptr<DynamicImage> MakeDocumentThumbnail(
not_null<DocumentData*> document,
Data::FileOrigin origin) {
return std::make_shared<VideoThumbnail>(
document,
origin,
false,
false);
}
std::shared_ptr<DynamicImage> MakeDocumentThumbnailCenterCrop(
not_null<DocumentData*> document,
FullMsgId fullId) {
@@ -51,6 +51,9 @@ class DynamicImage;
[[nodiscard]] std::shared_ptr<DynamicImage> MakeDocumentThumbnail(
not_null<DocumentData*> document,
FullMsgId fullId);
[[nodiscard]] std::shared_ptr<DynamicImage> MakeDocumentThumbnail(
not_null<DocumentData*> document,
Data::FileOrigin origin);
[[nodiscard]] std::shared_ptr<DynamicImage> MakeDocumentThumbnailCenterCrop(
not_null<DocumentData*> document,
FullMsgId fullId);