Support effects API, show icon in info.
This commit is contained in:
parent
ee4f83ffde
commit
f762634036
14 changed files with 437 additions and 286 deletions
|
@ -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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue