mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 14:38:15 +00:00
Support effects API, show icon in info.
This commit is contained in:
@@ -264,6 +264,7 @@ Reactions::Reactions(not_null<Session*> owner)
|
||||
kRefreshFullListEach
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshDefault();
|
||||
requestEffects();
|
||||
}, _lifetime);
|
||||
|
||||
_owner->session().changes().messageUpdates(
|
||||
@@ -343,6 +344,12 @@ void Reactions::refreshTags() {
|
||||
requestTags();
|
||||
}
|
||||
|
||||
void Reactions::refreshEffects() {
|
||||
if (_effects.empty()) {
|
||||
requestEffects();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Reaction> &Reactions::list(Type type) const {
|
||||
switch (type) {
|
||||
case Type::Active: return _active;
|
||||
@@ -352,6 +359,7 @@ const std::vector<Reaction> &Reactions::list(Type type) const {
|
||||
case Type::MyTags:
|
||||
return _myTags.find((SavedSublist*)nullptr)->second.tags;
|
||||
case Type::Tags: return _tags;
|
||||
case Type::Effects: return _effects;
|
||||
}
|
||||
Unexpected("Type in Reactions::list.");
|
||||
}
|
||||
@@ -552,21 +560,45 @@ rpl::producer<ReactionId> Reactions::myTagRenamed() const {
|
||||
return _myTagRenamed.events();
|
||||
}
|
||||
|
||||
rpl::producer<> Reactions::effectsUpdates() const {
|
||||
return _effectsUpdated.events();
|
||||
}
|
||||
|
||||
void Reactions::preloadReactionImageFor(const ReactionId &emoji) {
|
||||
if (!emoji.emoji().isEmpty()) {
|
||||
preloadImageFor(emoji);
|
||||
}
|
||||
}
|
||||
|
||||
void Reactions::preloadEffectImageFor(EffectId id) {
|
||||
preloadImageFor({ DocumentId(id) });
|
||||
}
|
||||
|
||||
void Reactions::preloadImageFor(const ReactionId &id) {
|
||||
if (_images.contains(id) || id.emoji().isEmpty()) {
|
||||
if (_images.contains(id)) {
|
||||
return;
|
||||
}
|
||||
auto &set = _images.emplace(id).first->second;
|
||||
const auto i = ranges::find(_available, id, &Reaction::id);
|
||||
const auto document = (i == end(_available))
|
||||
set.effect = (id.custom() != 0);
|
||||
const auto i = set.effect
|
||||
? ranges::find(_effects, id, &Reaction::id)
|
||||
: ranges::find(_available, id, &Reaction::id);
|
||||
const auto document = (i == end(set.effect ? _effects : _available))
|
||||
? nullptr
|
||||
: i->centerIcon
|
||||
? i->centerIcon
|
||||
: i->selectAnimation.get();
|
||||
if (document) {
|
||||
loadImage(set, document, !i->centerIcon);
|
||||
} else if (!_waitingForList) {
|
||||
_waitingForList = true;
|
||||
if (document || (set.effect && i != end(_effects))) {
|
||||
if (!set.effect || i->centerIcon) {
|
||||
loadImage(set, document, !i->centerIcon);
|
||||
} else {
|
||||
generateImage(set, i->title);
|
||||
}
|
||||
} else if (set.effect && !_waitingForEffects) {
|
||||
_waitingForEffects = true;
|
||||
refreshEffects();
|
||||
} else if (!set.effect && !_waitingForReactions) {
|
||||
_waitingForReactions = true;
|
||||
refreshDefault();
|
||||
}
|
||||
}
|
||||
@@ -597,14 +629,24 @@ void Reactions::preloadAnimationsFor(const ReactionId &id) {
|
||||
preload(i->aroundAnimation);
|
||||
}
|
||||
|
||||
QImage Reactions::resolveImageFor(
|
||||
const ReactionId &emoji,
|
||||
ImageSize size) {
|
||||
const auto i = _images.find(emoji);
|
||||
QImage Reactions::resolveReactionImageFor(const ReactionId &emoji) {
|
||||
Expects(!emoji.custom());
|
||||
|
||||
return resolveImageFor(emoji);
|
||||
}
|
||||
|
||||
QImage Reactions::resolveEffectImageFor(EffectId id) {
|
||||
return resolveImageFor({ DocumentId(id) });
|
||||
}
|
||||
|
||||
QImage Reactions::resolveImageFor(const ReactionId &id) {
|
||||
const auto i = _images.find(id);
|
||||
if (i == end(_images)) {
|
||||
preloadImageFor(emoji);
|
||||
preloadImageFor(id);
|
||||
}
|
||||
auto &set = (i != end(_images)) ? i->second : _images[emoji];
|
||||
auto &set = (i != end(_images)) ? i->second : _images[id];
|
||||
set.effect = (id.custom() != 0);
|
||||
|
||||
const auto resolve = [&](QImage &image, int size) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto frameSize = set.fromSelectAnimation
|
||||
@@ -634,21 +676,18 @@ QImage Reactions::resolveImageFor(
|
||||
}
|
||||
image.setDevicePixelRatio(factor);
|
||||
};
|
||||
if (set.bottomInfo.isNull() && set.icon) {
|
||||
resolve(set.bottomInfo, st::reactionInfoImage);
|
||||
resolve(set.inlineList, st::reactionInlineImage);
|
||||
if (set.image.isNull() && set.icon) {
|
||||
resolve(
|
||||
set.image,
|
||||
set.effect ? st::effectInfoImage : st::reactionInlineImage);
|
||||
crl::async([icon = std::move(set.icon)]{});
|
||||
}
|
||||
switch (size) {
|
||||
case ImageSize::BottomInfo: return set.bottomInfo;
|
||||
case ImageSize::InlineList: return set.inlineList;
|
||||
}
|
||||
Unexpected("ImageSize in Reactions::resolveImageFor.");
|
||||
return set.image;
|
||||
}
|
||||
|
||||
void Reactions::resolveImages() {
|
||||
void Reactions::resolveReactionImages() {
|
||||
for (auto &[id, set] : _images) {
|
||||
if (!set.bottomInfo.isNull() || set.icon || set.media) {
|
||||
if (set.effect || !set.image.isNull() || set.icon || set.media) {
|
||||
continue;
|
||||
}
|
||||
const auto i = ranges::find(_available, id, &Reaction::id);
|
||||
@@ -666,14 +705,38 @@ void Reactions::resolveImages() {
|
||||
}
|
||||
}
|
||||
|
||||
void Reactions::resolveEffectImages() {
|
||||
for (auto &[id, set] : _images) {
|
||||
if (!set.effect || !set.image.isNull() || set.icon || set.media) {
|
||||
continue;
|
||||
}
|
||||
const auto i = ranges::find(_effects, id, &Reaction::id);
|
||||
const auto document = (i == end(_effects))
|
||||
? nullptr
|
||||
: i->centerIcon
|
||||
? i->centerIcon
|
||||
: nullptr;
|
||||
if (document) {
|
||||
loadImage(set, document, false);
|
||||
} else if (i != end(_effects)) {
|
||||
generateImage(set, i->title);
|
||||
} else {
|
||||
LOG(("API Error: Effect '%1' not found!"
|
||||
).arg(ReactionIdToLog(id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Reactions::loadImage(
|
||||
ImageSet &set,
|
||||
not_null<DocumentData*> document,
|
||||
bool fromSelectAnimation) {
|
||||
if (!set.bottomInfo.isNull() || set.icon) {
|
||||
if (!set.image.isNull() || set.icon) {
|
||||
return;
|
||||
} else if (!set.media) {
|
||||
set.fromSelectAnimation = fromSelectAnimation;
|
||||
if (!set.effect) {
|
||||
set.fromSelectAnimation = fromSelectAnimation;
|
||||
}
|
||||
set.media = document->createMediaView();
|
||||
set.media->checkStickerLarge();
|
||||
}
|
||||
@@ -687,6 +750,26 @@ void Reactions::loadImage(
|
||||
}
|
||||
}
|
||||
|
||||
void Reactions::generateImage(ImageSet &set, const QString &emoji) {
|
||||
Expects(set.effect);
|
||||
|
||||
const auto e = Ui::Emoji::Find(emoji);
|
||||
Assert(e != nullptr);
|
||||
|
||||
const auto large = Ui::Emoji::GetSizeLarge();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
auto image = QImage(large, large, QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(factor);
|
||||
image.fill(Qt::transparent);
|
||||
{
|
||||
QPainter p(&image);
|
||||
Ui::Emoji::Draw(p, e, large, 0, 0);
|
||||
}
|
||||
const auto size = st::effectInfoImage;
|
||||
set.image = image.scaled(size * factor, size * factor);
|
||||
set.image.setDevicePixelRatio(factor);
|
||||
}
|
||||
|
||||
void Reactions::setAnimatedIcon(ImageSet &set) {
|
||||
const auto size = style::ConvertScale(kSizeForDownscale);
|
||||
set.icon = Ui::MakeAnimatedIcon({
|
||||
@@ -840,6 +923,25 @@ void Reactions::requestTags() {
|
||||
|
||||
}
|
||||
|
||||
void Reactions::requestEffects() {
|
||||
if (_effectsRequestId) {
|
||||
return;
|
||||
}
|
||||
auto &api = _owner->session().api();
|
||||
_effectsRequestId = api.request(MTPmessages_GetAvailableEffects(
|
||||
MTP_int(_effectsHash)
|
||||
)).done([=](const MTPmessages_AvailableEffects &result) {
|
||||
_effectsRequestId = 0;
|
||||
result.match([&](const MTPDmessages_availableEffects &data) {
|
||||
updateEffects(data);
|
||||
}, [&](const MTPDmessages_availableEffectsNotModified &) {
|
||||
});
|
||||
}).fail([=] {
|
||||
_effectsRequestId = 0;
|
||||
_effectsHash = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Reactions::updateTop(const MTPDmessages_reactions &data) {
|
||||
_topHash = data.vhash().v;
|
||||
_topIds = ListFromMTP(data);
|
||||
@@ -881,9 +983,9 @@ void Reactions::updateDefault(const MTPDmessages_availableReactions &data) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_waitingForList) {
|
||||
_waitingForList = false;
|
||||
resolveImages();
|
||||
if (_waitingForReactions) {
|
||||
_waitingForReactions = false;
|
||||
resolveReactionImages();
|
||||
}
|
||||
defaultUpdated();
|
||||
}
|
||||
@@ -939,6 +1041,32 @@ void Reactions::updateTags(const MTPDmessages_reactions &data) {
|
||||
_tagsUpdated.fire({});
|
||||
}
|
||||
|
||||
void Reactions::updateEffects(const MTPDmessages_availableEffects &data) {
|
||||
_effectsHash = data.vhash().v;
|
||||
|
||||
const auto &list = data.veffects().v;
|
||||
const auto toCache = [&](DocumentData *document) {
|
||||
if (document) {
|
||||
_iconsCache.emplace(document, document->createMediaView());
|
||||
}
|
||||
};
|
||||
for (const auto &document : data.vdocuments().v) {
|
||||
toCache(_owner->processDocument(document));
|
||||
}
|
||||
_effects.clear();
|
||||
_effects.reserve(list.size());
|
||||
for (const auto &effect : list) {
|
||||
if (const auto parsed = parse(effect)) {
|
||||
_effects.push_back(*parsed);
|
||||
}
|
||||
}
|
||||
if (_waitingForEffects) {
|
||||
_waitingForEffects = false;
|
||||
resolveEffectImages();
|
||||
}
|
||||
effectsUpdated();
|
||||
}
|
||||
|
||||
void Reactions::recentUpdated() {
|
||||
_topRefreshTimer.callOnce(kTopRequestDelay);
|
||||
_recentUpdated.fire({});
|
||||
@@ -969,6 +1097,10 @@ void Reactions::tagsUpdated() {
|
||||
_tagsUpdated.fire({});
|
||||
}
|
||||
|
||||
void Reactions::effectsUpdated() {
|
||||
_effectsUpdated.fire({});
|
||||
}
|
||||
|
||||
not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
|
||||
return static_cast<CustomEmojiManager::Listener*>(this);
|
||||
}
|
||||
@@ -1111,35 +1243,73 @@ void Reactions::resolve(const ReactionId &id) {
|
||||
}
|
||||
|
||||
std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
||||
return entry.match([&](const MTPDavailableReaction &data) {
|
||||
const auto emoji = qs(data.vreaction());
|
||||
const auto known = (Ui::Emoji::Find(emoji) != nullptr);
|
||||
if (!known) {
|
||||
LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji));
|
||||
}
|
||||
return known
|
||||
? std::make_optional(Reaction{
|
||||
.id = ReactionId{ emoji },
|
||||
.title = qs(data.vtitle()),
|
||||
//.staticIcon = _owner->processDocument(data.vstatic_icon()),
|
||||
.appearAnimation = _owner->processDocument(
|
||||
data.vappear_animation()),
|
||||
.selectAnimation = _owner->processDocument(
|
||||
data.vselect_animation()),
|
||||
//.activateAnimation = _owner->processDocument(
|
||||
// data.vactivate_animation()),
|
||||
//.activateEffects = _owner->processDocument(
|
||||
// data.veffect_animation()),
|
||||
.centerIcon = (data.vcenter_icon()
|
||||
? _owner->processDocument(*data.vcenter_icon()).get()
|
||||
: nullptr),
|
||||
.aroundAnimation = (data.varound_animation()
|
||||
? _owner->processDocument(
|
||||
*data.varound_animation()).get()
|
||||
: nullptr),
|
||||
.active = !data.is_inactive(),
|
||||
})
|
||||
: std::nullopt;
|
||||
const auto &data = entry.data();
|
||||
const auto emoji = qs(data.vreaction());
|
||||
const auto known = (Ui::Emoji::Find(emoji) != nullptr);
|
||||
if (!known) {
|
||||
LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji));
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::make_optional(Reaction{
|
||||
.id = ReactionId{ emoji },
|
||||
.title = qs(data.vtitle()),
|
||||
//.staticIcon = _owner->processDocument(data.vstatic_icon()),
|
||||
.appearAnimation = _owner->processDocument(
|
||||
data.vappear_animation()),
|
||||
.selectAnimation = _owner->processDocument(
|
||||
data.vselect_animation()),
|
||||
//.activateAnimation = _owner->processDocument(
|
||||
// data.vactivate_animation()),
|
||||
//.activateEffects = _owner->processDocument(
|
||||
// data.veffect_animation()),
|
||||
.centerIcon = (data.vcenter_icon()
|
||||
? _owner->processDocument(*data.vcenter_icon()).get()
|
||||
: nullptr),
|
||||
.aroundAnimation = (data.varound_animation()
|
||||
? _owner->processDocument(*data.varound_animation()).get()
|
||||
: nullptr),
|
||||
.active = !data.is_inactive(),
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<Reaction> Reactions::parse(const MTPAvailableEffect &entry) {
|
||||
const auto &data = entry.data();
|
||||
const auto emoji = qs(data.vemoticon());
|
||||
const auto known = (Ui::Emoji::Find(emoji) != nullptr);
|
||||
if (!known) {
|
||||
LOG(("API Error: Unknown emoji in effects: %1").arg(emoji));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto id = DocumentId(data.vid().v);
|
||||
const auto document = _owner->document(id);
|
||||
if (!document->sticker()) {
|
||||
LOG(("API Error: Bad sticker in effects: %1").arg(id));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto aroundId = data.veffect_animation_id().value_or_empty();
|
||||
const auto around = aroundId
|
||||
? _owner->document(aroundId).get()
|
||||
: nullptr;
|
||||
if (around && !around->sticker()) {
|
||||
LOG(("API Error: Bad sticker in effects around: %1").arg(aroundId));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto iconId = data.vstatic_icon_id().value_or_empty();
|
||||
const auto icon = iconId ? _owner->document(iconId).get() : nullptr;
|
||||
if (icon && !icon->sticker()) {
|
||||
LOG(("API Error: Bad sticker in effects icon: %1").arg(iconId));
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::make_optional(Reaction{
|
||||
.id = ReactionId{ id },
|
||||
.title = emoji,
|
||||
.appearAnimation = document,
|
||||
.selectAnimation = document,
|
||||
.centerIcon = icon,
|
||||
.aroundAnimation = around,
|
||||
.active = true,
|
||||
.effect = true,
|
||||
.premium = data.is_premium_required(),
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user