Support effects API, show icon in info.

This commit is contained in:
John Preston 2024-05-06 23:34:28 +04:00
parent ee4f83ffde
commit f762634036
14 changed files with 437 additions and 286 deletions

View file

@ -264,6 +264,7 @@ Reactions::Reactions(not_null<Session*> owner)
kRefreshFullListEach kRefreshFullListEach
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
refreshDefault(); refreshDefault();
requestEffects();
}, _lifetime); }, _lifetime);
_owner->session().changes().messageUpdates( _owner->session().changes().messageUpdates(
@ -343,6 +344,12 @@ void Reactions::refreshTags() {
requestTags(); requestTags();
} }
void Reactions::refreshEffects() {
if (_effects.empty()) {
requestEffects();
}
}
const std::vector<Reaction> &Reactions::list(Type type) const { const std::vector<Reaction> &Reactions::list(Type type) const {
switch (type) { switch (type) {
case Type::Active: return _active; case Type::Active: return _active;
@ -352,6 +359,7 @@ const std::vector<Reaction> &Reactions::list(Type type) const {
case Type::MyTags: case Type::MyTags:
return _myTags.find((SavedSublist*)nullptr)->second.tags; return _myTags.find((SavedSublist*)nullptr)->second.tags;
case Type::Tags: return _tags; case Type::Tags: return _tags;
case Type::Effects: return _effects;
} }
Unexpected("Type in Reactions::list."); Unexpected("Type in Reactions::list.");
} }
@ -552,21 +560,45 @@ rpl::producer<ReactionId> Reactions::myTagRenamed() const {
return _myTagRenamed.events(); 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) { void Reactions::preloadImageFor(const ReactionId &id) {
if (_images.contains(id) || id.emoji().isEmpty()) { if (_images.contains(id)) {
return; return;
} }
auto &set = _images.emplace(id).first->second; auto &set = _images.emplace(id).first->second;
const auto i = ranges::find(_available, id, &Reaction::id); set.effect = (id.custom() != 0);
const auto document = (i == end(_available)) 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 ? nullptr
: i->centerIcon : i->centerIcon
? i->centerIcon ? i->centerIcon
: i->selectAnimation.get(); : i->selectAnimation.get();
if (document) { if (document || (set.effect && i != end(_effects))) {
loadImage(set, document, !i->centerIcon); if (!set.effect || i->centerIcon) {
} else if (!_waitingForList) { loadImage(set, document, !i->centerIcon);
_waitingForList = true; } else {
generateImage(set, i->title);
}
} else if (set.effect && !_waitingForEffects) {
_waitingForEffects = true;
refreshEffects();
} else if (!set.effect && !_waitingForReactions) {
_waitingForReactions = true;
refreshDefault(); refreshDefault();
} }
} }
@ -597,14 +629,24 @@ void Reactions::preloadAnimationsFor(const ReactionId &id) {
preload(i->aroundAnimation); preload(i->aroundAnimation);
} }
QImage Reactions::resolveImageFor( QImage Reactions::resolveReactionImageFor(const ReactionId &emoji) {
const ReactionId &emoji, Expects(!emoji.custom());
ImageSize size) {
const auto i = _images.find(emoji); 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)) { 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 resolve = [&](QImage &image, int size) {
const auto factor = style::DevicePixelRatio(); const auto factor = style::DevicePixelRatio();
const auto frameSize = set.fromSelectAnimation const auto frameSize = set.fromSelectAnimation
@ -634,21 +676,18 @@ QImage Reactions::resolveImageFor(
} }
image.setDevicePixelRatio(factor); image.setDevicePixelRatio(factor);
}; };
if (set.bottomInfo.isNull() && set.icon) { if (set.image.isNull() && set.icon) {
resolve(set.bottomInfo, st::reactionInfoImage); resolve(
resolve(set.inlineList, st::reactionInlineImage); set.image,
set.effect ? st::effectInfoImage : st::reactionInlineImage);
crl::async([icon = std::move(set.icon)]{}); crl::async([icon = std::move(set.icon)]{});
} }
switch (size) { return set.image;
case ImageSize::BottomInfo: return set.bottomInfo;
case ImageSize::InlineList: return set.inlineList;
}
Unexpected("ImageSize in Reactions::resolveImageFor.");
} }
void Reactions::resolveImages() { void Reactions::resolveReactionImages() {
for (auto &[id, set] : _images) { for (auto &[id, set] : _images) {
if (!set.bottomInfo.isNull() || set.icon || set.media) { if (set.effect || !set.image.isNull() || set.icon || set.media) {
continue; continue;
} }
const auto i = ranges::find(_available, id, &Reaction::id); 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( void Reactions::loadImage(
ImageSet &set, ImageSet &set,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool fromSelectAnimation) { bool fromSelectAnimation) {
if (!set.bottomInfo.isNull() || set.icon) { if (!set.image.isNull() || set.icon) {
return; return;
} else if (!set.media) { } else if (!set.media) {
set.fromSelectAnimation = fromSelectAnimation; if (!set.effect) {
set.fromSelectAnimation = fromSelectAnimation;
}
set.media = document->createMediaView(); set.media = document->createMediaView();
set.media->checkStickerLarge(); 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) { void Reactions::setAnimatedIcon(ImageSet &set) {
const auto size = style::ConvertScale(kSizeForDownscale); const auto size = style::ConvertScale(kSizeForDownscale);
set.icon = Ui::MakeAnimatedIcon({ 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) { void Reactions::updateTop(const MTPDmessages_reactions &data) {
_topHash = data.vhash().v; _topHash = data.vhash().v;
_topIds = ListFromMTP(data); _topIds = ListFromMTP(data);
@ -881,9 +983,9 @@ void Reactions::updateDefault(const MTPDmessages_availableReactions &data) {
} }
} }
} }
if (_waitingForList) { if (_waitingForReactions) {
_waitingForList = false; _waitingForReactions = false;
resolveImages(); resolveReactionImages();
} }
defaultUpdated(); defaultUpdated();
} }
@ -939,6 +1041,32 @@ void Reactions::updateTags(const MTPDmessages_reactions &data) {
_tagsUpdated.fire({}); _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() { void Reactions::recentUpdated() {
_topRefreshTimer.callOnce(kTopRequestDelay); _topRefreshTimer.callOnce(kTopRequestDelay);
_recentUpdated.fire({}); _recentUpdated.fire({});
@ -969,6 +1097,10 @@ void Reactions::tagsUpdated() {
_tagsUpdated.fire({}); _tagsUpdated.fire({});
} }
void Reactions::effectsUpdated() {
_effectsUpdated.fire({});
}
not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() { not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
return static_cast<CustomEmojiManager::Listener*>(this); return static_cast<CustomEmojiManager::Listener*>(this);
} }
@ -1111,35 +1243,73 @@ void Reactions::resolve(const ReactionId &id) {
} }
std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) { std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
return entry.match([&](const MTPDavailableReaction &data) { const auto &data = entry.data();
const auto emoji = qs(data.vreaction()); const auto emoji = qs(data.vreaction());
const auto known = (Ui::Emoji::Find(emoji) != nullptr); const auto known = (Ui::Emoji::Find(emoji) != nullptr);
if (!known) { if (!known) {
LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji)); LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji));
} return std::nullopt;
return known }
? std::make_optional(Reaction{ return std::make_optional(Reaction{
.id = ReactionId{ emoji }, .id = ReactionId{ emoji },
.title = qs(data.vtitle()), .title = qs(data.vtitle()),
//.staticIcon = _owner->processDocument(data.vstatic_icon()), //.staticIcon = _owner->processDocument(data.vstatic_icon()),
.appearAnimation = _owner->processDocument( .appearAnimation = _owner->processDocument(
data.vappear_animation()), data.vappear_animation()),
.selectAnimation = _owner->processDocument( .selectAnimation = _owner->processDocument(
data.vselect_animation()), data.vselect_animation()),
//.activateAnimation = _owner->processDocument( //.activateAnimation = _owner->processDocument(
// data.vactivate_animation()), // data.vactivate_animation()),
//.activateEffects = _owner->processDocument( //.activateEffects = _owner->processDocument(
// data.veffect_animation()), // data.veffect_animation()),
.centerIcon = (data.vcenter_icon() .centerIcon = (data.vcenter_icon()
? _owner->processDocument(*data.vcenter_icon()).get() ? _owner->processDocument(*data.vcenter_icon()).get()
: nullptr), : nullptr),
.aroundAnimation = (data.varound_animation() .aroundAnimation = (data.varound_animation()
? _owner->processDocument( ? _owner->processDocument(*data.varound_animation()).get()
*data.varound_animation()).get() : nullptr),
: nullptr), .active = !data.is_inactive(),
.active = !data.is_inactive(), });
}) }
: std::nullopt;
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(),
}); });
} }

View file

@ -37,6 +37,8 @@ struct Reaction {
DocumentData *aroundAnimation = nullptr; DocumentData *aroundAnimation = nullptr;
int count = 0; int count = 0;
bool active = false; bool active = false;
bool effect = false;
bool premium = false;
}; };
struct PossibleItemReactionsRef { struct PossibleItemReactionsRef {
@ -80,6 +82,7 @@ public:
void refreshMyTags(SavedSublist *sublist = nullptr); void refreshMyTags(SavedSublist *sublist = nullptr);
void refreshMyTagsDelayed(); void refreshMyTagsDelayed();
void refreshTags(); void refreshTags();
void refreshEffects();
enum class Type { enum class Type {
Active, Active,
@ -88,6 +91,7 @@ public:
All, All,
MyTags, MyTags,
Tags, Tags,
Effects,
}; };
[[nodiscard]] const std::vector<Reaction> &list(Type type) const; [[nodiscard]] const std::vector<Reaction> &list(Type type) const;
[[nodiscard]] const std::vector<MyTagInfo> &myTagsInfo() const; [[nodiscard]] const std::vector<MyTagInfo> &myTagsInfo() const;
@ -108,16 +112,15 @@ public:
[[nodiscard]] rpl::producer<> myTagsUpdates() const; [[nodiscard]] rpl::producer<> myTagsUpdates() const;
[[nodiscard]] rpl::producer<> tagsUpdates() const; [[nodiscard]] rpl::producer<> tagsUpdates() const;
[[nodiscard]] rpl::producer<ReactionId> myTagRenamed() const; [[nodiscard]] rpl::producer<ReactionId> myTagRenamed() const;
[[nodiscard]] rpl::producer<> effectsUpdates() const;
void preloadReactionImageFor(const ReactionId &emoji);
[[nodiscard]] QImage resolveReactionImageFor(const ReactionId &emoji);
void preloadEffectImageFor(EffectId id);
[[nodiscard]] QImage resolveEffectImageFor(EffectId id);
enum class ImageSize {
BottomInfo,
InlineList,
};
void preloadImageFor(const ReactionId &emoji);
void preloadAnimationsFor(const ReactionId &emoji); void preloadAnimationsFor(const ReactionId &emoji);
[[nodiscard]] QImage resolveImageFor(
const ReactionId &emoji,
ImageSize size);
void send(not_null<HistoryItem*> item, bool addToRecent); void send(not_null<HistoryItem*> item, bool addToRecent);
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const; [[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
@ -139,11 +142,11 @@ public:
private: private:
struct ImageSet { struct ImageSet {
QImage bottomInfo; QImage image;
QImage inlineList;
std::shared_ptr<DocumentMedia> media; std::shared_ptr<DocumentMedia> media;
std::unique_ptr<Ui::AnimatedIcon> icon; std::unique_ptr<Ui::AnimatedIcon> icon;
bool fromSelectAnimation = false; bool fromSelectAnimation = false;
bool effect = false;
}; };
struct TagsBySublist { struct TagsBySublist {
TagsBySublist() = default; TagsBySublist() = default;
@ -169,6 +172,7 @@ private:
void requestGeneric(); void requestGeneric();
void requestMyTags(SavedSublist *sublist = nullptr); void requestMyTags(SavedSublist *sublist = nullptr);
void requestTags(); void requestTags();
void requestEffects();
void updateTop(const MTPDmessages_reactions &data); void updateTop(const MTPDmessages_reactions &data);
void updateRecent(const MTPDmessages_reactions &data); void updateRecent(const MTPDmessages_reactions &data);
@ -178,11 +182,13 @@ private:
SavedSublist *sublist, SavedSublist *sublist,
const MTPDmessages_savedReactionTags &data); const MTPDmessages_savedReactionTags &data);
void updateTags(const MTPDmessages_reactions &data); void updateTags(const MTPDmessages_reactions &data);
void updateEffects(const MTPDmessages_availableEffects &data);
void recentUpdated(); void recentUpdated();
void defaultUpdated(); void defaultUpdated();
void myTagsUpdated(); void myTagsUpdated();
void tagsUpdated(); void tagsUpdated();
void effectsUpdated();
[[nodiscard]] std::optional<Reaction> resolveById(const ReactionId &id); [[nodiscard]] std::optional<Reaction> resolveById(const ReactionId &id);
[[nodiscard]] std::vector<Reaction> resolveByIds( [[nodiscard]] std::vector<Reaction> resolveByIds(
@ -203,13 +209,19 @@ private:
[[nodiscard]] std::optional<Reaction> parse( [[nodiscard]] std::optional<Reaction> parse(
const MTPAvailableReaction &entry); const MTPAvailableReaction &entry);
[[nodiscard]] std::optional<Reaction> parse(
const MTPAvailableEffect &entry);
void preloadImageFor(const ReactionId &id);
[[nodiscard]] QImage resolveImageFor(const ReactionId &id);
void loadImage( void loadImage(
ImageSet &set, ImageSet &set,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool fromSelectAnimation); bool fromSelectAnimation);
void generateImage(ImageSet &set, const QString &emoji);
void setAnimatedIcon(ImageSet &set); void setAnimatedIcon(ImageSet &set);
void resolveImages(); void resolveReactionImages();
void resolveEffectImages();
void downloadTaskFinished(); void downloadTaskFinished();
void repaintCollected(); void repaintCollected();
@ -233,6 +245,7 @@ private:
std::vector<ReactionId> _topIds; std::vector<ReactionId> _topIds;
base::flat_set<ReactionId> _unresolvedTop; base::flat_set<ReactionId> _unresolvedTop;
std::vector<not_null<DocumentData*>> _genericAnimations; std::vector<not_null<DocumentData*>> _genericAnimations;
std::vector<Reaction> _effects;
ReactionId _favoriteId; ReactionId _favoriteId;
ReactionId _unresolvedFavoriteId; ReactionId _unresolvedFavoriteId;
std::optional<Reaction> _favorite; std::optional<Reaction> _favorite;
@ -249,6 +262,7 @@ private:
rpl::event_stream<SavedSublist*> _myTagsUpdated; rpl::event_stream<SavedSublist*> _myTagsUpdated;
rpl::event_stream<> _tagsUpdated; rpl::event_stream<> _tagsUpdated;
rpl::event_stream<ReactionId> _myTagRenamed; rpl::event_stream<ReactionId> _myTagRenamed;
rpl::event_stream<> _effectsUpdated;
// We need &i->second stay valid while inserting new items. // We need &i->second stay valid while inserting new items.
// So we use std::map instead of base::flat_map here. // So we use std::map instead of base::flat_map here.
@ -271,9 +285,13 @@ private:
mtpRequestId _tagsRequestId = 0; mtpRequestId _tagsRequestId = 0;
uint64 _tagsHash = 0; uint64 _tagsHash = 0;
mtpRequestId _effectsRequestId = 0;
int32 _effectsHash = 0;
base::flat_map<ReactionId, ImageSet> _images; base::flat_map<ReactionId, ImageSet> _images;
rpl::lifetime _imagesLoadLifetime; rpl::lifetime _imagesLoadLifetime;
bool _waitingForList = false; bool _waitingForReactions = false;
bool _waitingForEffects = false;
base::flat_map<FullMsgId, mtpRequestId> _sentRequests; base::flat_map<FullMsgId, mtpRequestId> _sentRequests;

View file

@ -168,7 +168,7 @@ void SearchTags::fill(
.selected = ranges::contains(selected, id), .selected = ranges::contains(selected, id),
}); });
if (!customId) { if (!customId) {
_owner->reactions().preloadImageFor(id); _owner->reactions().preloadReactionImageFor(id);
} }
}; };
if (!premium) { if (!premium) {
@ -335,9 +335,7 @@ void SearchTags::paint(
paintBackground(p, geometry, tag); paintBackground(p, geometry, tag);
paintText(p, geometry, tag); paintText(p, geometry, tag);
if (!tag.custom && !tag.promo && tag.image.isNull()) { if (!tag.custom && !tag.promo && tag.image.isNull()) {
tag.image = _owner->reactions().resolveImageFor( tag.image = _owner->reactions().resolveReactionImageFor(tag.id);
tag.id,
::Data::Reactions::ImageSize::InlineList);
} }
const auto inner = geometry.marginsRemoved(padding); const auto inner = geometry.marginsRemoved(padding);
const auto image = QRect( const auto image = QRect(

View file

@ -365,6 +365,7 @@ HistoryItem::HistoryItem(
.from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0), .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0),
.date = data.vdate().v, .date = data.vdate().v,
.shortcutId = data.vquick_reply_shortcut_id().value_or_empty(), .shortcutId = data.vquick_reply_shortcut_id().value_or_empty(),
.effectId = data.veffect().value_or_empty(),
}) { }) {
_boostsApplied = data.vfrom_boosts_applied().value_or_empty(); _boostsApplied = data.vfrom_boosts_applied().value_or_empty();
@ -681,7 +682,8 @@ HistoryItem::HistoryItem(
: history->peer) : history->peer)
, _flags(FinalizeMessageFlags(history, fields.flags)) , _flags(FinalizeMessageFlags(history, fields.flags))
, _date(fields.date) , _date(fields.date)
, _shortcutId(fields.shortcutId) { , _shortcutId(fields.shortcutId)
, _effectId(fields.effectId) {
if (isHistoryEntry() && IsClientMsgId(id)) { if (isHistoryEntry() && IsClientMsgId(id)) {
_history->registerClientSideMessage(this); _history->registerClientSideMessage(this);
} }

View file

@ -74,6 +74,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_message_reactions.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue. #include "data/data_peer_values.h" // Data::AmPremiumValue.
#include "data/data_premium_limits.h" // Data::PremiumLimits. #include "data/data_premium_limits.h" // Data::PremiumLimits.
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
@ -1398,7 +1399,7 @@ int HistoryWidget::itemTopForHighlight(
if (heightLeft >= 0) { if (heightLeft >= 0) {
return std::max(itemTop - (heightLeft / 2), 0); return std::max(itemTop - (heightLeft / 2), 0);
} else if (reactionCenter >= 0) { } else if (reactionCenter >= 0) {
const auto maxSize = st::reactionInfoImage; const auto maxSize = st::reactionInlineImage;
// Show message right till the bottom. // Show message right till the bottom.
const auto forBottom = itemTop + viewHeight - visibleAreaHeight; const auto forBottom = itemTop + viewHeight - visibleAreaHeight;
@ -2375,6 +2376,8 @@ void HistoryWidget::showHistory(
} }
} }
session().data().reactions().refreshEffects();
_scroll->hide(); _scroll->hide();
_list = _scroll->setOwnedWidget( _list = _scroll->setOwnedWidget(
object_ptr<HistoryInner>(this, _scroll, controller(), _history)); object_ptr<HistoryInner>(this, _scroll, controller(), _history));

View file

@ -30,14 +30,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
struct BottomInfo::Reaction { struct BottomInfo::Effect {
mutable std::unique_ptr<Ui::ReactionFlyAnimation> animation; mutable std::unique_ptr<Ui::ReactionFlyAnimation> animation;
mutable QImage image; mutable QImage image;
ReactionId id; EffectId id = 0;
QString countText;
int count = 0;
int countTextWidth = 0;
bool chosen = false;
}; };
BottomInfo::BottomInfo( BottomInfo::BottomInfo(
@ -58,17 +54,11 @@ void BottomInfo::update(Data &&data, int availableWidth) {
} }
} }
int BottomInfo::countReactionsMaxWidth() const { int BottomInfo::countEffectMaxWidth() const {
auto result = 0; auto result = 0;
for (const auto &reaction : _reactions) { if (_effect) {
result += st::reactionInfoSize; result += st::reactionInfoSize;
if (reaction.countTextWidth > 0) { result += st::reactionInfoBetween;
result += st::reactionInfoSkip
+ reaction.countTextWidth
+ st::reactionInfoDigitSkip;
} else {
result += st::reactionInfoBetween;
}
} }
if (result) { if (result) {
result += (st::reactionInfoSkip - st::reactionInfoBetween); result += (st::reactionInfoSkip - st::reactionInfoBetween);
@ -76,19 +66,14 @@ int BottomInfo::countReactionsMaxWidth() const {
return result; return result;
} }
int BottomInfo::countReactionsHeight(int newWidth) const { int BottomInfo::countEffectHeight(int newWidth) const {
const auto left = 0; const auto left = 0;
auto x = 0; auto x = 0;
auto y = 0; auto y = 0;
auto widthLeft = newWidth; auto widthLeft = newWidth;
for (const auto &reaction : _reactions) { if (_effect) {
const auto add = (reaction.countTextWidth > 0) const auto add = st::reactionInfoBetween;
? st::reactionInfoDigitSkip const auto width = st::reactionInfoSize;
: st::reactionInfoBetween;
const auto width = st::reactionInfoSize
+ (reaction.countTextWidth > 0
? (st::reactionInfoSkip + reaction.countTextWidth)
: 0);
if (x > left && widthLeft < width) { if (x > left && widthLeft < width) {
x = left; x = left;
y += st::msgDateFont->height; y += st::msgDateFont->height;
@ -107,7 +92,7 @@ int BottomInfo::firstLineWidth() const {
if (height() == minHeight()) { if (height() == minHeight()) {
return width(); return width();
} }
return maxWidth() - _reactionsMaxWidth; return maxWidth() - _effectMaxWidth;
} }
bool BottomInfo::isWide() const { bool BottomInfo::isWide() const {
@ -115,14 +100,14 @@ bool BottomInfo::isWide() const {
|| !_data.author.isEmpty() || !_data.author.isEmpty()
|| !_views.isEmpty() || !_views.isEmpty()
|| !_replies.isEmpty() || !_replies.isEmpty()
|| !_reactions.empty(); || _effect;
} }
TextState BottomInfo::textState( TextState BottomInfo::textState(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
QPoint position) const { QPoint position) const {
auto result = TextState(item); auto result = TextState(item);
if (const auto link = revokeReactionLink(item, position)) { if (const auto link = replayEffectLink(item, position)) {
result.link = link; result.link = link;
return result; return result;
} }
@ -172,32 +157,26 @@ TextState BottomInfo::textState(
return result; return result;
} }
ClickHandlerPtr BottomInfo::revokeReactionLink( ClickHandlerPtr BottomInfo::replayEffectLink(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
QPoint position) const { QPoint position) const {
if (_reactions.empty()) { if (!_effect) {
return nullptr; return nullptr;
} }
auto left = 0; auto left = 0;
auto top = 0; auto top = 0;
auto available = width(); auto available = width();
if (height() != minHeight()) { if (height() != minHeight()) {
available = std::min(available, _reactionsMaxWidth); available = std::min(available, _effectMaxWidth);
left += width() - available; left += width() - available;
top += st::msgDateFont->height; top += st::msgDateFont->height;
} }
auto x = left; auto x = left;
auto y = top; auto y = top;
auto widthLeft = available; auto widthLeft = available;
for (const auto &reaction : _reactions) { if (_effect) {
const auto chosen = reaction.chosen; const auto add = st::reactionInfoBetween;
const auto add = (reaction.countTextWidth > 0) const auto width = st::reactionInfoSize;
? st::reactionInfoDigitSkip
: st::reactionInfoBetween;
const auto width = st::reactionInfoSize
+ (reaction.countTextWidth > 0
? (st::reactionInfoSkip + reaction.countTextWidth)
: 0);
if (x > left && widthLeft < width) { if (x > left && widthLeft < width) {
x = left; x = left;
y += st::msgDateFont->height; y += st::msgDateFont->height;
@ -208,11 +187,11 @@ ClickHandlerPtr BottomInfo::revokeReactionLink(
y, y,
st::reactionInfoSize, st::reactionInfoSize,
st::msgDateFont->height); st::msgDateFont->height);
if (chosen && image.contains(position)) { if (image.contains(position)) {
if (!_revokeLink) { if (!_replayLink) {
_revokeLink = revokeReactionLink(item); _replayLink = replayEffectLink(item);
} }
return _revokeLink; return _replayLink;
} }
x += width + add; x += width + add;
widthLeft -= width + add; widthLeft -= width + add;
@ -220,25 +199,16 @@ ClickHandlerPtr BottomInfo::revokeReactionLink(
return nullptr; return nullptr;
} }
ClickHandlerPtr BottomInfo::revokeReactionLink( ClickHandlerPtr BottomInfo::replayEffectLink(
not_null<const HistoryItem*> item) const { not_null<const HistoryItem*> item) const {
const auto itemId = item->fullId(); const auto itemId = item->fullId();
const auto sessionId = item->history()->session().uniqueId(); const auto sessionId = item->history()->session().uniqueId();
return std::make_shared<LambdaClickHandler>([=]( return std::make_shared<LambdaClickHandler>([=](
ClickContext context) { ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
if (controller->session().uniqueId() == sessionId) { controller->showToast("playing nice effect..");
auto &owner = controller->session().data(); AssertIsDebug();
if (const auto item = owner.message(itemId)) {
const auto chosen = item->chosenReactions();
if (!chosen.empty()) {
item->toggleReaction(
chosen.front(),
HistoryItem::ReactionSource::Existing);
}
}
}
} }
}); });
} }
@ -340,20 +310,20 @@ void BottomInfo::paint(
firstLineBottom + st::historyViewsTop, firstLineBottom + st::historyViewsTop,
outerWidth); outerWidth);
} }
if (!_reactions.empty()) { if (_effect) {
auto left = position.x(); auto left = position.x();
auto top = position.y(); auto top = position.y();
auto available = width(); auto available = width();
if (height() != minHeight()) { if (height() != minHeight()) {
available = std::min(available, _reactionsMaxWidth); available = std::min(available, _effectMaxWidth);
left += width() - available; left += width() - available;
top += st::msgDateFont->height; top += st::msgDateFont->height;
} }
paintReactions(p, position, left, top, available, context); paintEffect(p, position, left, top, available, context);
} }
} }
void BottomInfo::paintReactions( void BottomInfo::paintEffect(
Painter &p, Painter &p,
QPoint origin, QPoint origin,
int left, int left,
@ -369,52 +339,33 @@ void BottomInfo::paintReactions(
auto x = left; auto x = left;
auto y = top; auto y = top;
auto widthLeft = availableWidth; auto widthLeft = availableWidth;
for (const auto &reaction : _reactions) { if (_effect) {
if (context.reactionInfo const auto animating = (_effect->animation != nullptr);
&& reaction.animation const auto add = st::reactionInfoBetween;
&& reaction.animation->finished()) { const auto width = st::reactionInfoSize;
reaction.animation = nullptr;
}
const auto animating = (reaction.animation != nullptr);
const auto add = (reaction.countTextWidth > 0)
? st::reactionInfoDigitSkip
: st::reactionInfoBetween;
const auto width = st::reactionInfoSize
+ (reaction.countTextWidth > 0
? (st::reactionInfoSkip + reaction.countTextWidth)
: 0);
if (x > left && widthLeft < width) { if (x > left && widthLeft < width) {
x = left; x = left;
y += st::msgDateFont->height; y += st::msgDateFont->height;
widthLeft = availableWidth; widthLeft = availableWidth;
} }
if (reaction.image.isNull()) { if (_effect->image.isNull()) {
reaction.image = _reactionsOwner->resolveImageFor( _effect->image = _reactionsOwner->resolveEffectImageFor(
reaction.id, _effect->id);
::Data::Reactions::ImageSize::BottomInfo);
} }
const auto image = QRect( const auto image = QRect(
x + (st::reactionInfoSize - st::reactionInfoImage) / 2, x + (st::reactionInfoSize - st::effectInfoImage) / 2,
y + (st::msgDateFont->height - st::reactionInfoImage) / 2, y + (st::msgDateFont->height - st::effectInfoImage) / 2,
st::reactionInfoImage, st::effectInfoImage,
st::reactionInfoImage); st::effectInfoImage);
const auto skipImage = animating if (!_effect->image.isNull()) {
&& (reaction.count < 2 || !reaction.animation->flying()); p.drawImage(image.topLeft(), _effect->image);
if (!reaction.image.isNull() && !skipImage) {
p.drawImage(image.topLeft(), reaction.image);
} }
if (animating) { if (animating) {
animations.push_back({ animations.push_back({
.animation = reaction.animation.get(), .animation = _effect->animation.get(),
.target = image, .target = image,
}); });
} }
if (reaction.countTextWidth > 0) {
p.drawText(
x + st::reactionInfoSize + st::reactionInfoSkip,
y + st::msgDateFont->ascent,
reaction.countText);
}
x += width + add; x += width + add;
widthLeft -= width + add; widthLeft -= width + add;
} }
@ -448,18 +399,18 @@ QSize BottomInfo::countCurrentSize(int newWidth) {
const auto dateHeight = (_data.flags & Data::Flag::Sponsored) const auto dateHeight = (_data.flags & Data::Flag::Sponsored)
? 0 ? 0
: st::msgDateFont->height; : st::msgDateFont->height;
const auto noReactionsWidth = maxWidth() - _reactionsMaxWidth; const auto noReactionsWidth = maxWidth() - _effectMaxWidth;
accumulate_min(newWidth, std::max(noReactionsWidth, _reactionsMaxWidth)); accumulate_min(newWidth, std::max(noReactionsWidth, _effectMaxWidth));
return QSize( return QSize(
newWidth, newWidth,
dateHeight + countReactionsHeight(newWidth)); dateHeight + countEffectHeight(newWidth));
} }
void BottomInfo::layout() { void BottomInfo::layout() {
layoutDateText(); layoutDateText();
layoutViewsText(); layoutViewsText();
layoutRepliesText(); layoutRepliesText();
layoutReactionsText(); layoutEffectText();
initDimensions(); initDimensions();
} }
@ -520,33 +471,12 @@ void BottomInfo::layoutRepliesText() {
Ui::NameTextOptions()); Ui::NameTextOptions());
} }
void BottomInfo::layoutReactionsText() { void BottomInfo::layoutEffectText() {
if (_data.reactions.empty()) { if (!_data.effectId) {
_reactions.clear(); _effect = nullptr;
return; return;
} }
auto sorted = ranges::views::all( _effect = std::make_unique<Effect>(prepareEffectWithId(_data.effectId));
_data.reactions
) | ranges::views::transform([](const MessageReaction &reaction) {
return not_null{ &reaction };
}) | ranges::to_vector;
ranges::sort(
sorted,
std::greater<>(),
&MessageReaction::count);
auto reactions = std::vector<Reaction>();
reactions.reserve(sorted.size());
for (const auto &reaction : sorted) {
const auto &id = reaction->id;
const auto i = ranges::find(_reactions, id, &Reaction::id);
reactions.push_back((i != end(_reactions))
? std::move(*i)
: prepareReactionWithId(id));
reactions.back().chosen = reaction->my;
setReactionCount(reactions.back(), reaction->count);
}
_reactions = std::move(reactions);
} }
QSize BottomInfo::countOptimalSize() { QSize BottomInfo::countOptimalSize() {
@ -571,69 +501,42 @@ QSize BottomInfo::countOptimalSize() {
if (_data.flags & Data::Flag::Pinned) { if (_data.flags & Data::Flag::Pinned) {
width += st::historyPinWidth; width += st::historyPinWidth;
} }
_reactionsMaxWidth = countReactionsMaxWidth(); _effectMaxWidth = countEffectMaxWidth();
width += _reactionsMaxWidth; width += _effectMaxWidth;
const auto dateHeight = (_data.flags & Data::Flag::Sponsored) const auto dateHeight = (_data.flags & Data::Flag::Sponsored)
? 0 ? 0
: st::msgDateFont->height; : st::msgDateFont->height;
return QSize(width, dateHeight); return QSize(width, dateHeight);
} }
BottomInfo::Reaction BottomInfo::prepareReactionWithId( BottomInfo::Effect BottomInfo::prepareEffectWithId(EffectId id) {
const ReactionId &id) { auto result = Effect{ .id = id };
auto result = Reaction{ .id = id }; _reactionsOwner->preloadEffectImageFor(id);
_reactionsOwner->preloadImageFor(id);
return result; return result;
} }
void BottomInfo::setReactionCount(Reaction &reaction, int count) { void BottomInfo::animateEffect(
if (reaction.count == count) { Ui::ReactionFlyAnimationArgs &&args,
return;
}
reaction.count = count;
reaction.countText = (count > 1)
? Lang::FormatCountToShort(count).string
: QString();
reaction.countTextWidth = (count > 1)
? st::msgDateFont->width(reaction.countText)
: 0;
}
void BottomInfo::animateReaction(
Ui::ReactionFlyAnimationArgs &&args,
Fn<void()> repaint) { Fn<void()> repaint) {
const auto i = ranges::find(_reactions, args.id, &Reaction::id); if (!_effect || args.id.custom() != _effect->id) {
if (i == end(_reactions)) {
return; return;
} }
i->animation = std::make_unique<Ui::ReactionFlyAnimation>( _effect->animation = std::make_unique<Ui::ReactionFlyAnimation>(
_reactionsOwner, _reactionsOwner,
args.translated(QPoint(width(), height())), args.translated(QPoint(width(), height())),
std::move(repaint), std::move(repaint),
st::reactionInfoImage); st::effectInfoImage);
} }
auto BottomInfo::takeReactionAnimations() auto BottomInfo::takeEffectAnimation()
-> base::flat_map<ReactionId, std::unique_ptr<Ui::ReactionFlyAnimation>> { -> std::unique_ptr<Ui::ReactionFlyAnimation> {
auto result = base::flat_map< return _effect ? std::move(_effect->animation) : nullptr;
ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>();
for (auto &reaction : _reactions) {
if (reaction.animation) {
result.emplace(reaction.id, std::move(reaction.animation));
}
}
return result;
} }
void BottomInfo::continueReactionAnimations(base::flat_map< void BottomInfo::continueEffectAnimation(
ReactionId, std::unique_ptr<Ui::ReactionFlyAnimation> animation) {
std::unique_ptr<Ui::ReactionFlyAnimation>> animations) { if (_effect) {
for (auto &[id, animation] : animations) { _effect->animation = std::move(animation);
const auto i = ranges::find(_reactions, id, &Reaction::id);
if (i != end(_reactions)) {
i->animation = std::move(animation);
}
} }
} }
@ -643,9 +546,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
auto result = BottomInfo::Data(); auto result = BottomInfo::Data();
result.date = message->dateTime(); result.date = message->dateTime();
if (message->embedReactionsInBottomInfo()) { result.effectId = item->effectId();
result.reactions = item->reactions();
}
if (message->hasOutLayout()) { if (message->hasOutLayout()) {
result.flags |= Flag::OutLayout; result.flags |= Flag::OutLayout;
} }

View file

@ -20,8 +20,6 @@ class ReactionFlyAnimation;
namespace Data { namespace Data {
class Reactions; class Reactions;
struct ReactionId;
struct MessageReaction;
} // namespace Data } // namespace Data
namespace HistoryView { namespace HistoryView {
@ -33,8 +31,6 @@ struct TextState;
class BottomInfo final : public Object { class BottomInfo final : public Object {
public: public:
using ReactionId = ::Data::ReactionId;
using MessageReaction = ::Data::MessageReaction;
struct Data { struct Data {
enum class Flag : uchar { enum class Flag : uchar {
Edited = 0x01, Edited = 0x01,
@ -52,7 +48,7 @@ public:
QDateTime date; QDateTime date;
QString author; QString author;
std::vector<MessageReaction> reactions; EffectId effectId = 0;
std::optional<int> views; std::optional<int> views;
std::optional<int> replies; std::optional<int> replies;
std::optional<int> forwardsCount; std::optional<int> forwardsCount;
@ -78,29 +74,26 @@ public:
bool inverted, bool inverted,
const PaintContext &context) const; const PaintContext &context) const;
void animateReaction( void animateEffect(
Ui::ReactionFlyAnimationArgs &&args, Ui::ReactionFlyAnimationArgs &&args,
Fn<void()> repaint); Fn<void()> repaint);
[[nodiscard]] auto takeReactionAnimations() [[nodiscard]] auto takeEffectAnimation()
-> base::flat_map< -> std::unique_ptr<Ui::ReactionFlyAnimation>;
ReactionId, void continueEffectAnimation(
std::unique_ptr<Ui::ReactionFlyAnimation>>; std::unique_ptr<Ui::ReactionFlyAnimation> animation);
void continueReactionAnimations(base::flat_map<
ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>> animations);
private: private:
struct Reaction; struct Effect;
void layout(); void layout();
void layoutDateText(); void layoutDateText();
void layoutViewsText(); void layoutViewsText();
void layoutRepliesText(); void layoutRepliesText();
void layoutReactionsText(); void layoutEffectText();
[[nodiscard]] int countReactionsMaxWidth() const; [[nodiscard]] int countEffectMaxWidth() const;
[[nodiscard]] int countReactionsHeight(int newWidth) const; [[nodiscard]] int countEffectHeight(int newWidth) const;
void paintReactions( void paintEffect(
Painter &p, Painter &p,
QPoint origin, QPoint origin,
int left, int left,
@ -111,13 +104,11 @@ private:
QSize countOptimalSize() override; QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override; QSize countCurrentSize(int newWidth) override;
void setReactionCount(Reaction &reaction, int count); [[nodiscard]] Effect prepareEffectWithId(EffectId id);
[[nodiscard]] Reaction prepareReactionWithId( [[nodiscard]] ClickHandlerPtr replayEffectLink(
const ReactionId &id);
[[nodiscard]] ClickHandlerPtr revokeReactionLink(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
QPoint position) const; QPoint position) const;
[[nodiscard]] ClickHandlerPtr revokeReactionLink( [[nodiscard]] ClickHandlerPtr replayEffectLink(
not_null<const HistoryItem*> item) const; not_null<const HistoryItem*> item) const;
const not_null<::Data::Reactions*> _reactionsOwner; const not_null<::Data::Reactions*> _reactionsOwner;
@ -125,9 +116,9 @@ private:
Ui::Text::String _authorEditedDate; Ui::Text::String _authorEditedDate;
Ui::Text::String _views; Ui::Text::String _views;
Ui::Text::String _replies; Ui::Text::String _replies;
std::vector<Reaction> _reactions; std::unique_ptr<Effect> _effect;
mutable ClickHandlerPtr _revokeLink; mutable ClickHandlerPtr _replayLink;
int _reactionsMaxWidth = 0; int _effectMaxWidth = 0;
bool _authorElided = false; bool _authorElided = false;
}; };

View file

@ -1038,7 +1038,7 @@ void EditTagBox(
customId, customId,
[=] { field->update(); }); [=] { field->update(); });
} else { } else {
owner->reactions().preloadImageFor(id); owner->reactions().preloadReactionImageFor(id);
} }
field->paintRequest() | rpl::start_with_next([=](QRect clip) { field->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(field); auto p = QPainter(field);
@ -1053,9 +1053,8 @@ void EditTagBox(
}); });
} else { } else {
if (state->image.isNull()) { if (state->image.isNull()) {
state->image = owner->reactions().resolveImageFor( state->image = owner->reactions().resolveReactionImageFor(
id, id);
::Data::Reactions::ImageSize::InlineList);
} }
if (!state->image.isNull()) { if (!state->image.isNull()) {
const auto size = st::reactionInlineSize; const auto size = st::reactionInlineSize;

View file

@ -1770,6 +1770,17 @@ auto Element::takeReactionAnimations()
return {}; return {};
} }
void Element::animateEffect(Ui::ReactionFlyAnimationArgs &&args) {
}
void Element::animateUnreadEffect() {
}
auto Element::takeEffectAnimation()
-> std::unique_ptr<Ui::ReactionFlyAnimation> {
return nullptr;
}
Element::~Element() { Element::~Element() {
// Delete media while owner still exists. // Delete media while owner still exists.
clearSpecialOnlyEmoji(); clearSpecialOnlyEmoji();

View file

@ -545,6 +545,11 @@ public:
Data::ReactionId, Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>; std::unique_ptr<Ui::ReactionFlyAnimation>>;
virtual void animateEffect(Ui::ReactionFlyAnimationArgs &&args);
void animateUnreadEffect();
[[nodiscard]] virtual auto takeEffectAnimation()
-> std::unique_ptr<Ui::ReactionFlyAnimation>;
void overrideMedia(std::unique_ptr<Media> media); void overrideMedia(std::unique_ptr<Media> media);
virtual bool consumeHorizontalScroll(QPoint position, int delta) { virtual bool consumeHorizontalScroll(QPoint position, int delta) {

View file

@ -423,6 +423,7 @@ Message::Message(
: base::flat_map< : base::flat_map<
Data::ReactionId, Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>>(); std::unique_ptr<Ui::ReactionFlyAnimation>>();
auto animation = replacing ? replacing->takeEffectAnimation() : nullptr;
if (!animations.empty()) { if (!animations.empty()) {
const auto repainter = [=] { repaint(); }; const auto repainter = [=] { repaint(); };
for (const auto &[id, animation] : animations) { for (const auto &[id, animation] : animations) {
@ -430,10 +431,11 @@ Message::Message(
} }
if (_reactions) { if (_reactions) {
_reactions->continueAnimations(std::move(animations)); _reactions->continueAnimations(std::move(animations));
} else {
_bottomInfo.continueReactionAnimations(std::move(animations));
} }
} }
if (animation) {
_bottomInfo.continueEffectAnimation(std::move(animation));
}
if (data->isSponsored()) { if (data->isSponsored()) {
const auto &session = data->history()->session(); const auto &session = data->history()->session();
const auto details = session.sponsoredMessages().lookupDetails( const auto details = session.sponsoredMessages().lookupDetails(
@ -582,9 +584,6 @@ void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
return; return;
} }
const auto animateInBottomInfo = [&](QPoint bottomRight) {
_bottomInfo.animateReaction(args.translated(-bottomRight), repainter);
};
if (bubble) { if (bubble) {
auto entry = logEntryOriginal(); auto entry = logEntryOriginal();
@ -609,6 +608,50 @@ void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
_reactions->animate(args.translated(-reactionsPosition), repainter); _reactions->animate(args.translated(-reactionsPosition), repainter);
return; return;
} }
}
}
void Message::animateEffect(Ui::ReactionFlyAnimationArgs &&args) {
const auto item = data();
const auto media = this->media();
auto g = countGeometry();
if (g.width() < 1 || isHidden()) {
return;
}
const auto repainter = [=] { repaint(); };
const auto bubble = drawBubble();
const auto reactionsInBubble = _reactions && embedReactionsInBubble();
const auto mediaDisplayed = media && media->isDisplayed();
const auto keyboard = item->inlineReplyKeyboard();
auto keyboardHeight = 0;
if (keyboard) {
keyboardHeight = keyboard->naturalHeight();
g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
}
const auto animateInBottomInfo = [&](QPoint bottomRight) {
_bottomInfo.animateEffect(args.translated(-bottomRight), repainter);
};
if (bubble) {
auto entry = logEntryOriginal();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto inner = g;
if (_comments) {
inner.setHeight(inner.height() - st::historyCommentsButtonHeight);
}
auto trect = inner.marginsRemoved(st::msgPadding);
const auto reactionsTop = (reactionsInBubble && !_viewButton)
? st::mediaInBubbleSkip
: 0;
const auto reactionsHeight = reactionsInBubble
? (reactionsTop + _reactions->height())
: 0;
if (_viewButton) { if (_viewButton) {
const auto belowInfo = _viewButton->belowMessageInfo(); const auto belowInfo = _viewButton->belowMessageInfo();
const auto infoHeight = reactionsInBubble const auto infoHeight = reactionsInBubble
@ -653,9 +696,15 @@ auto Message::takeReactionAnimations()
-> base::flat_map< -> base::flat_map<
Data::ReactionId, Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>> { std::unique_ptr<Ui::ReactionFlyAnimation>> {
return _reactions if (_reactions) {
? _reactions->takeAnimations() return _reactions->takeAnimations();
: _bottomInfo.takeReactionAnimations(); }
return {};
}
auto Message::takeEffectAnimation()
-> std::unique_ptr<Ui::ReactionFlyAnimation> {
return _bottomInfo.takeEffectAnimation();
} }
QSize Message::performCountOptimalSize() { QSize Message::performCountOptimalSize() {

View file

@ -158,6 +158,10 @@ public:
Data::ReactionId, Data::ReactionId,
std::unique_ptr<Ui::ReactionFlyAnimation>> override; std::unique_ptr<Ui::ReactionFlyAnimation>> override;
void animateEffect(Ui::ReactionFlyAnimationArgs &&args) override;
auto takeEffectAnimation()
-> std::unique_ptr<Ui::ReactionFlyAnimation> override;
QRect innerGeometry() const override; QRect innerGeometry() const override;
[[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const; [[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const;

View file

@ -182,7 +182,7 @@ InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) {
customId, customId,
_customEmojiRepaint); _customEmojiRepaint);
} else { } else {
_owner->preloadImageFor(id); _owner->preloadReactionImageFor(id);
} }
return result; return result;
} }
@ -439,9 +439,7 @@ void InlineList::paint(
} }
} }
if (!button.custom && button.image.isNull()) { if (!button.custom && button.image.isNull()) {
button.image = _owner->resolveImageFor( button.image = _owner->resolveReactionImageFor(button.id);
button.id,
::Data::Reactions::ImageSize::InlineList);
} }
const auto textFg = !inbubble const auto textFg = !inbubble

View file

@ -904,6 +904,8 @@ reactionMainAppearShift: 20px;
reactionCollapseFadeThreshold: 40px; reactionCollapseFadeThreshold: 40px;
reactionFlyUp: 50px; reactionFlyUp: 50px;
effectInfoImage: 12px;
searchInChatMultiSelectItem: MultiSelectItem(defaultMultiSelectItem) { searchInChatMultiSelectItem: MultiSelectItem(defaultMultiSelectItem) {
maxWidth: 200px; maxWidth: 200px;
} }