Allow wearing collectibles from emoji status.

This commit is contained in:
John Preston 2025-01-09 14:34:31 +04:00
parent d0132c0f7b
commit fecddb5203
23 changed files with 278 additions and 68 deletions

View file

@ -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";

View file

@ -265,7 +265,7 @@ struct IconSelector {
const auto manager = &controller->session().data().customEmojiManager();
auto factory = [=](DocumentId id, Fn<void()> repaint)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
-> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
if (id == kDefaultIconId) {
return std::make_unique<DefaultIconEmoji>(
@ -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());

View file

@ -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<Ui::RpWidget> 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,

View file

@ -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 }};

View file

@ -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;

View file

@ -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<Data::EmojiStatusCollectible> 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<EmojiStatusId> DocumentListToRecent(
const std::vector<DocumentId> &documents) {
return documents | ranges::views::transform([](DocumentId id) {
return EmojiStatusId{ .documentId = id };
}) | ranges::to_vector;
}
EmojiListWidget::EmojiListWidget(
QWidget *parent,
not_null<Window::SessionController*> 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<OverEmoji>(&_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<DocumentId> &customRecentList) {
const std::vector<EmojiStatusId> &customRecentList) {
clearSelection();
fillRecentFrom(customRecentList);
resizeToWidth(width());
@ -1094,7 +1109,8 @@ void EmojiListWidget::fillRecent() {
}
}
void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
void EmojiListWidget::fillRecentFrom(
const std::vector<EmojiStatusId> &list) {
const auto test = session().isTestMode();
_recent.clear();
_recent.reserve(list.size());
@ -1113,11 +1129,17 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &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<EmojiPtr>(&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<RecentEmojiDocument>(
&_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<RecentEmojiDocument>(
&_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<DocumentData*> 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<Ui::Text::CustomEmoji*> 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<CustomOne>();
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<void(uint64 setId, bool installed)> push,
GroupStickersPlace place) {
@ -2638,8 +2741,11 @@ void EmojiListWidget::setSelected(OverState newSelected) {
} else if (_previewShown && _pressed != _selected) {
if (const auto over = std::get_if<OverEmoji>(&_selected)) {
if (const auto custom = lookupCustomEmoji(over)) {
const auto document = custom.document;
_pressed = _selected;
_show->showMediaPreview(custom->stickerSetOrigin(), custom);
_show->showMediaPreview(
document->stickerSetOrigin(),
document);
}
}
}

View file

@ -83,12 +83,15 @@ enum class EmojiListMode {
MessageEffects,
};
[[nodiscard]] std::vector<EmojiStatusId> DocumentListToRecent(
const std::vector<DocumentId> &documents);
struct EmojiListDescriptor {
std::shared_ptr<Show> show;
EmojiListMode mode = EmojiListMode::Full;
Fn<QColor()> customTextColor;
Fn<bool()> paused;
std::vector<DocumentId> customRecentList;
std::vector<EmojiStatusId> customRecentList;
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
DocumentId,
Fn<void()>)> customRecentFactory;
@ -137,7 +140,7 @@ public:
[[nodiscard]] rpl::producer<> jumpedToPremium() const;
[[nodiscard]] rpl::producer<> escapes() const;
void provideRecent(const std::vector<DocumentId> &customRecentList);
void provideRecent(const std::vector<EmojiStatusId> &customRecentList);
void prepareExpanding();
void paintExpanding(
@ -186,6 +189,7 @@ private:
bool collapsed = false;
};
struct CustomOne {
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
not_null<Ui::Text::CustomEmoji*> custom;
not_null<DocumentData*> document;
EmojiPtr emoji = nullptr;
@ -253,6 +257,14 @@ private:
int finalHeight = 0;
bool expanding = false;
};
struct ResolvedCustom {
DocumentData *document = nullptr;
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
explicit operator bool() const {
return document != nullptr;
}
};
template <typename Callback>
bool enumerateSections(Callback callback) const;
@ -271,6 +283,7 @@ private:
Visible,
Hidden,
};
void refreshEmojiStatusCollectibles();
void refreshMegagroupStickers(
Fn<void(uint64 setId, bool installed)> 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<const OverEmoji*> over);
[[nodiscard]] FileChosen lookupChosen(
not_null<DocumentData*> 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<DocumentId> &list);
void fillRecentFrom(const std::vector<EmojiStatusId> &list);
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
not_null<DocumentData*> document,
uint64 setId);

View file

@ -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);
}());

View file

@ -1017,7 +1017,7 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
}
void TabbedSelector::provideRecentEmoji(
const std::vector<DocumentId> &customRecentList) {
const std::vector<EmojiStatusId> &customRecentList) {
for (const auto &tab : _tabs) {
if (tab.type() == SelectorTab::Emoji) {
const auto emoji = static_cast<EmojiListWidget*>(tab.widget());

View file

@ -62,6 +62,7 @@ struct FileChosen {
not_null<DocumentData*> document;
Api::SendOptions options;
Ui::MessageSendingAnimationFrom messageSendingFrom;
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
TextWithTags caption;
};
@ -154,7 +155,7 @@ public:
void refreshStickers();
void setCurrentPeer(PeerData *peer);
void provideRecentEmoji(
const std::vector<DocumentId> &customRecentList);
const std::vector<EmojiStatusId> &customRecentList);
void hideFinished();
void showStarted();

View file

@ -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<EmojiStatusId> &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);
}

View file

@ -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<not_null<PeerData*>, mtpRequestId> _sentRequests;

View file

@ -814,6 +814,25 @@ void Stickers::setPackAndEmoji(
}
}
not_null<StickersSet*> 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<StickersSet>(
&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,

View file

@ -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<StickersType> updated() const;
[[nodiscard]] rpl::producer<> updated(StickersType type) const;
@ -244,6 +247,8 @@ public:
[[nodiscard]] auto getEmojiListFromSet(not_null<DocumentData*> document)
-> std::optional<std::vector<not_null<EmojiPtr>>>;
[[nodiscard]] not_null<StickersSet*> collectibleSet();
not_null<StickersSet*> feedSet(const MTPStickerSet &data);
not_null<StickersSet*> feedSet(const MTPStickerSetCovered &data);
not_null<StickersSet*> feedSetFull(const MTPDmessages_stickerSet &data);

View file

@ -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<HistoryItem*> 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<HistoryItem*> item) {

View file

@ -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 },

View file

@ -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,

View file

@ -70,7 +70,7 @@ EmojiStatusPanel::~EmojiStatusPanel() {
}
}
void EmojiStatusPanel::setChooseFilter(Fn<bool(DocumentId)> filter) {
void EmojiStatusPanel::setChooseFilter(Fn<bool(EmojiStatusId)> 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<DocumentId>();
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<DocumentId> &&list) {
auto tmp = std::vector<EmojiStatusId>();
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<ChatHelpers::TabbedPanel>(
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<Window::SessionController*> 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<Data::Session*> owner,
not_null<Ui::RpWidget*> 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,

View file

@ -40,7 +40,7 @@ public:
EmojiStatusPanel();
~EmojiStatusPanel();
void setChooseFilter(Fn<bool(DocumentId)> filter);
void setChooseFilter(Fn<bool(EmojiStatusId)> filter);
void show(
not_null<Window::SessionController*> controller,
@ -56,6 +56,7 @@ public:
Fn<QColor()> 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<Window::SessionController*> controller,
DocumentId chosenId) const;
EmojiStatusId chosenId) const;
void startAnimation(
not_null<Data::Session*> owner,
not_null<Ui::RpWidget*> body,
DocumentId statusId,
EmojiStatusId statusId,
Ui::MessageSendingAnimationFrom from);
base::unique_qptr<ChatHelpers::TabbedPanel> _panel;
Fn<QColor()> _customTextColor;
Fn<bool(DocumentId)> _chooseFilter;
Fn<bool(EmojiStatusId)> _chooseFilter;
QPointer<QWidget> _panelButton;
std::unique_ptr<Ui::EmojiFlyAnimation> _animation;
rpl::event_stream<CustomChosen> _someCustomChosen;

View file

@ -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<void()> repaint) {
return manager->create(id, std::move(repaint), tag);
},

View file

@ -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 }};

View file

@ -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;
}

View file

@ -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)