Show, send and receive interactions in single custom emoji.

This commit is contained in:
John Preston 2022-08-04 15:55:25 +03:00
parent 9e63fc5acd
commit e438cb57bc
18 changed files with 158 additions and 88 deletions

View file

@ -69,60 +69,26 @@ EmojiInteractions::~EmojiInteractions() = default;
void EmojiInteractions::checkEdition(
not_null<HistoryItem*> item,
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map) {
const auto &pack = _session->emojiStickersPack();
const auto i = map.find(item);
if (i != end(map)
&& (i->second.front().emoji != chooseInteractionEmoji(item))) {
&& (i->second.front().emoji != pack.chooseInteractionEmoji(item))) {
map.erase(i);
}
}
EmojiPtr EmojiInteractions::chooseInteractionEmoji(
not_null<HistoryItem*> item) const {
return chooseInteractionEmoji(item->originalText().text);
}
EmojiPtr EmojiInteractions::chooseInteractionEmoji(
const QString &emoticon) const {
const auto emoji = Ui::Emoji::Find(emoticon);
if (!emoji) {
return nullptr;
}
const auto &pack = _session->emojiStickersPack();
if (!pack.animationsForEmoji(emoji).empty()) {
return emoji;
}
if (const auto original = emoji->original(); original != emoji) {
if (!pack.animationsForEmoji(original).empty()) {
return original;
}
}
static const auto kHearts = {
QString::fromUtf8("\xf0\x9f\x92\x9b"),
QString::fromUtf8("\xf0\x9f\x92\x99"),
QString::fromUtf8("\xf0\x9f\x92\x9a"),
QString::fromUtf8("\xf0\x9f\x92\x9c"),
QString::fromUtf8("\xf0\x9f\xa7\xa1"),
QString::fromUtf8("\xf0\x9f\x96\xa4"),
QString::fromUtf8("\xf0\x9f\xa4\x8e"),
QString::fromUtf8("\xf0\x9f\xa4\x8d"),
};
return ranges::contains(kHearts, emoji->id())
? Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\xa4"))
: emoji;
}
void EmojiInteractions::startOutgoing(
not_null<const HistoryView::Element*> view) {
const auto item = view->data();
if (!item->isRegular() || !item->history()->peer->isUser()) {
return;
}
const auto &pack = _session->emojiStickersPack();
const auto emoticon = item->originalText().text;
const auto emoji = chooseInteractionEmoji(emoticon);
const auto emoji = pack.chooseInteractionEmoji(emoticon);
if (!emoji) {
return;
}
const auto &pack = _session->emojiStickersPack();
const auto &list = pack.animationsForEmoji(emoji);
if (list.empty()) {
return;
@ -168,11 +134,11 @@ void EmojiInteractions::startIncoming(
if (!item || !item->isRegular()) {
return;
}
const auto emoji = chooseInteractionEmoji(item);
if (!emoji || emoji != chooseInteractionEmoji(emoticon)) {
const auto &pack = _session->emojiStickersPack();
const auto emoji = pack.chooseInteractionEmoji(item);
if (!emoji || emoji != pack.chooseInteractionEmoji(emoticon)) {
return;
}
const auto &pack = _session->emojiStickersPack();
const auto &list = pack.animationsForEmoji(emoji);
if (list.empty()) {
return;
@ -215,8 +181,9 @@ void EmojiInteractions::startIncoming(
void EmojiInteractions::seenOutgoing(
not_null<PeerData*> peer,
const QString &emoticon) {
const auto &pack = _session->emojiStickersPack();
if (const auto i = _playsSent.find(peer); i != end(_playsSent)) {
if (const auto emoji = chooseInteractionEmoji(emoticon)) {
if (const auto emoji = pack.chooseInteractionEmoji(emoticon)) {
if (const auto j = i->second.find(emoji); j != end(i->second)) {
const auto last = j->second.lastDoneReceivedAt;
if (!last || last + kAcceptSeenSinceRequest > crl::now()) {

View file

@ -97,11 +97,6 @@ private:
};
[[nodiscard]] static CheckResult Combine(CheckResult a, CheckResult b);
[[nodiscard]] EmojiPtr chooseInteractionEmoji(
not_null<HistoryItem*> item) const;
[[nodiscard]] EmojiPtr chooseInteractionEmoji(
const QString &emoticon) const;
void check(crl::time now = 0);
[[nodiscard]] CheckResult checkAnimations(crl::time now);
[[nodiscard]] CheckResult checkAnimations(

View file

@ -214,13 +214,58 @@ std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {
return result;
}
EmojiPtr EmojiPack::chooseInteractionEmoji(
not_null<HistoryItem*> item) const {
return chooseInteractionEmoji(item->originalText().text);
}
EmojiPtr EmojiPack::chooseInteractionEmoji(
const QString &emoticon) const {
const auto emoji = Ui::Emoji::Find(emoticon);
if (!emoji) {
return nullptr;
}
if (!animationsForEmoji(emoji).empty()) {
return emoji;
}
if (const auto original = emoji->original(); original != emoji) {
if (!animationsForEmoji(original).empty()) {
return original;
}
}
static const auto kHearts = {
QString::fromUtf8("\xf0\x9f\x92\x9b"),
QString::fromUtf8("\xf0\x9f\x92\x99"),
QString::fromUtf8("\xf0\x9f\x92\x9a"),
QString::fromUtf8("\xf0\x9f\x92\x9c"),
QString::fromUtf8("\xf0\x9f\xa7\xa1"),
QString::fromUtf8("\xf0\x9f\x96\xa4"),
QString::fromUtf8("\xf0\x9f\xa4\x8e"),
QString::fromUtf8("\xf0\x9f\xa4\x8d"),
};
return ranges::contains(kHearts, emoji->id())
? Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\xa4"))
: emoji;
}
auto EmojiPack::animationsForEmoji(EmojiPtr emoji) const
-> const base::flat_map<int, not_null<DocumentData*>> & {
static const auto empty = base::flat_map<int, not_null<DocumentData*>>();
if (!emoji) {
return empty;
}
const auto i = _animations.find(emoji);
return (i != end(_animations)) ? i->second : empty;
}
bool EmojiPack::hasAnimationsFor(not_null<HistoryItem*> item) const {
return !animationsForEmoji(chooseInteractionEmoji(item)).empty();
}
bool EmojiPack::hasAnimationsFor(const QString &emoticon) const {
return !animationsForEmoji(chooseInteractionEmoji(emoticon)).empty();
}
std::unique_ptr<Lottie::SinglePlayer> EmojiPack::effectPlayer(
not_null<DocumentData*> document,
QByteArray data,
@ -371,6 +416,7 @@ void EmojiPack::applyAnimationsSet(const MTPDmessages_stickerSet &data) {
}
});
}
++_animationsVersion;
}
auto EmojiPack::collectAnimationsIndices(

View file

@ -70,8 +70,17 @@ public:
[[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji);
[[nodiscard]] std::shared_ptr<LargeEmojiImage> image(EmojiPtr emoji);
[[nodiscard]] EmojiPtr chooseInteractionEmoji(
not_null<HistoryItem*> item) const;
[[nodiscard]] EmojiPtr chooseInteractionEmoji(
const QString &emoticon) const;
[[nodiscard]] auto animationsForEmoji(EmojiPtr emoji) const
-> const base::flat_map<int, not_null<DocumentData*>> &;
[[nodiscard]] bool hasAnimationsFor(not_null<HistoryItem*> item) const;
[[nodiscard]] bool hasAnimationsFor(const QString &emoticon) const;
[[nodiscard]] int animationsVersion() const {
return _animationsVersion;
}
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> effectPlayer(
not_null<DocumentData*> document,
@ -109,6 +118,7 @@ private:
base::flat_set<not_null<HistoryItem*>> _onlyCustomItems;
int _animationsVersion = 0;
base::flat_map<
EmojiPtr,
base::flat_map<int, not_null<DocumentData*>>> _animations;

View file

@ -3106,7 +3106,7 @@ auto HistoryInner::prevItem(Element *view) -> Element* {
return nullptr;
} else if (const auto result = view->previousDisplayedInBlocks()) {
return result;
} else if (view->data()->history() == _history
} else if (view->history() == _history
&& _migrated
&& _history->loadedAtTop()
&& !_migrated->isEmpty()
@ -3121,7 +3121,7 @@ auto HistoryInner::nextItem(Element *view) -> Element* {
return nullptr;
} else if (const auto result = view->nextDisplayedInBlocks()) {
return result;
} else if (view->data()->history() == _migrated
} else if (view->history() == _migrated
&& _migrated->loadedAtBottom()
&& _history->loadedAtTop()
&& !_history->isEmpty()) {
@ -3730,9 +3730,9 @@ int HistoryInner::itemTop(const Element *view) const {
return -1;
}
auto top = (view->data()->history() == _history)
auto top = (view->history() == _history)
? historyTop()
: (view->data()->history() == _migrated
: (view->history() == _migrated
? migratedTop()
: -2);
return (top < 0) ? top : (top + view->y() + view->block()->y());
@ -4020,8 +4020,8 @@ void HistoryInner::applyDragSelection(
auto toblock = _dragSelTo->block()->indexInHistory();
auto toitem = _dragSelTo->indexInBlock();
if (_migrated) {
if (_dragSelFrom->data()->history() == _migrated) {
if (_dragSelTo->data()->history() == _migrated) {
if (_dragSelFrom->history() == _migrated) {
if (_dragSelTo->history() == _migrated) {
addSelectionRange(toItems, _migrated, fromblock, fromitem, toblock, toitem);
toblock = -1;
toitem = -1;
@ -4030,7 +4030,7 @@ void HistoryInner::applyDragSelection(
}
fromblock = 0;
fromitem = 0;
} else if (_dragSelTo->data()->history() == _migrated) { // wtf
} else if (_dragSelTo->history() == _migrated) { // wtf
toblock = -1;
toitem = -1;
}

View file

@ -6319,7 +6319,7 @@ void HistoryWidget::updatePinnedViewer() {
auto [view, offset] = _list->findViewForPinnedTracking(visibleBottom);
const auto lessThanId = !view
? (ServerMaxMsgId - 1)
: (view->data()->history() != _history)
: (view->history() != _history)
? (view->data()->id + (offset > 0 ? 1 : 0) - ServerMaxMsgId)
: (view->data()->id + (offset > 0 ? 1 : 0));
const auto lastClickedId = !_pinnedClickedId

View file

@ -976,7 +976,7 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
const auto context = list->elementContext();
AddPollActions(result, poll, item, context, list->controller());
} else if (!request.overSelection && view && !hasSelection) {
const auto owner = &view->data()->history()->owner();
const auto owner = &view->history()->owner();
const auto media = view->media();
const auto mediaHasTextForCopy = media && media->hasTextForCopy();
if (const auto document = media ? media->getDocument() : nullptr) {

View file

@ -579,9 +579,7 @@ void Element::refreshMedia(Element *replacing) {
&& Core::App().settings().largeEmoji()) {
_media = std::make_unique<UnwrappedMedia>(
this,
std::make_unique<CustomEmoji>(
this,
_data->onlyCustomEmoji()));
std::make_unique<CustomEmoji>(this, _data->onlyCustomEmoji()));
} else if (_data->isIsolatedEmoji()
&& Core::App().settings().largeEmoji()) {
const auto emoji = _data->isolatedEmoji();

View file

@ -57,7 +57,7 @@ Call::Call(
}
QSize Call::countOptimalSize() {
const auto user = _parent->data()->history()->peer->asUser();
const auto user = _parent->history()->peer->asUser();
const auto video = _video;
_link = std::make_shared<LambdaClickHandler>([=] {
if (user) {

View file

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/stickers/data_custom_emoji.h"
#include "main/main_session.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "chat_helpers/stickers_lottie.h"
#include "ui/chat/chat_style.h"
#include "ui/text/text_isolated_emoji.h"
@ -33,7 +35,7 @@ struct CustomEmojiSizeInfo {
};
[[nodiscard]] const base::flat_map<int, CustomEmojiSizeInfo> &SizesInfo() {
// size = i->second.scale * st::maxAnimatedEmojiSize.
// size = i->second.scale * Sticker::EmojiSize().width()
// CustomEmojiManager::SizeTag caching uses first ::EmojiInteraction-s.
using Info = CustomEmojiSizeInfo;
static auto result = base::flat_map<int, Info>{
@ -64,7 +66,7 @@ CustomEmoji::CustomEmoji(
: _parent(parent) {
Expects(!emoji.lines.empty());
const auto owner = &parent->data()->history()->owner();
const auto owner = &parent->history()->owner();
const auto manager = &owner->customEmojiManager();
const auto max = ranges::max_element(
emoji.lines,
@ -76,7 +78,8 @@ CustomEmoji::CustomEmoji(
const auto useCustomEmoji = (i == end(sizes));
const auto tag = EmojiSize(dimension);
_singleSize = !useCustomEmoji
? int(base::SafeRound(i->second.scale * st::maxAnimatedEmojiSize))
? int(base::SafeRound(
i->second.scale * Sticker::EmojiSize().width()))
: Data::FrameSizeFromTag(tag);
if (!useCustomEmoji) {
_cachingTag = i->second.tag;
@ -95,13 +98,7 @@ CustomEmoji::CustomEmoji(
const auto id = Data::ParseCustomEmojiData(data).id;
const auto document = owner->document(id);
if (document->sticker()) {
const auto skipPremiumEffect = false;
auto sticker = std::make_unique<Sticker>(
parent,
document,
skipPremiumEffect);
sticker->setCustomEmojiPart(_singleSize, _cachingTag);
_lines.back().push_back(std::move(sticker));
_lines.back().push_back(createStickerPart(document));
} else {
_lines.back().push_back(id);
manager->resolve(id, listener());
@ -118,13 +115,7 @@ void CustomEmoji::customEmojiResolveDone(not_null<DocumentData*> document) {
for (auto &line : _lines) {
for (auto &entry : line) {
if (entry == id) {
const auto skipPremiumEffect = false;
auto sticker = std::make_unique<Sticker>(
_parent,
document,
skipPremiumEffect);
sticker->setCustomEmojiPart(_singleSize, _cachingTag);
entry = std::move(sticker);
entry = createStickerPart(document);
} else if (v::is<DocumentId>(entry)) {
_resolving = true;
}
@ -132,13 +123,60 @@ void CustomEmoji::customEmojiResolveDone(not_null<DocumentData*> document) {
}
}
std::unique_ptr<Sticker> CustomEmoji::createStickerPart(
not_null<DocumentData*> document) const {
const auto skipPremiumEffect = false;
auto result = std::make_unique<Sticker>(
_parent,
document,
skipPremiumEffect);
result->setCustomEmojiPart(_singleSize, _cachingTag);
return result;
}
void CustomEmoji::refreshInteractionLink() {
if (_lines.size() != 1 || _lines.front().size() != 1) {
return;
}
const auto &pack = _parent->history()->session().emojiStickersPack();
const auto version = pack.animationsVersion();
if (_animationsCheckVersion == version) {
return;
}
_animationsCheckVersion = version;
if (pack.hasAnimationsFor(_parent->data())) {
const auto weak = base::make_weak(this);
_interactionLink = std::make_shared<LambdaClickHandler>([weak] {
if (const auto that = weak.get()) {
that->interactionLinkClicked();
}
});
} else {
_interactionLink = nullptr;
}
}
ClickHandlerPtr CustomEmoji::link() {
refreshInteractionLink();
return _interactionLink;
}
void CustomEmoji::interactionLinkClicked() {
const auto &entry = _lines.front().front();
if (const auto sticker = std::get_if<StickerPtr>(&entry)) {
if ((*sticker)->ready()) {
_parent->delegate()->elementStartInteraction(_parent);
}
}
}
CustomEmoji::~CustomEmoji() {
if (_hasHeavyPart) {
unloadHeavyPart();
_parent->checkHeavyPart();
}
if (_resolving) {
const auto owner = &_parent->data()->history()->owner();
const auto owner = &_parent->history()->owner();
owner->customEmojiManager().unregisterListener(listener());
}
}

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media_unwrapped.h"
#include "data/stickers/data_custom_emoji.h"
#include "base/weak_ptr.h"
namespace Ui::Text {
struct OnlyCustomEmoji;
@ -33,6 +34,7 @@ using LargeCustomEmoji = std::variant<
class CustomEmoji final
: public UnwrappedMedia::Content
, public base::has_weak_ptr
, private Data::CustomEmojiManager::Listener {
public:
CustomEmoji(
@ -46,6 +48,7 @@ public:
Painter &p,
const PaintContext &context,
const QRect &r) override;
ClickHandlerPtr link() override;
bool alwaysShowOutTimestamp() override {
return true;
@ -58,9 +61,6 @@ public:
void unloadHeavyPart() override;
private:
[[nodiscard]] not_null<Data::CustomEmojiManager::Listener*> listener() {
return this;
}
void paintElement(
Painter &p,
int x,
@ -83,12 +83,23 @@ private:
const PaintContext &context,
bool paused);
[[nodiscard]] not_null<Data::CustomEmojiManager::Listener*> listener() {
return this;
}
void customEmojiResolveDone(not_null<DocumentData*> document) override;
[[nodiscard]] std::unique_ptr<Sticker> createStickerPart(
not_null<DocumentData*> document) const;
void refreshInteractionLink();
void interactionLinkClicked();
const not_null<Element*> _parent;
std::vector<std::vector<LargeCustomEmoji>> _lines;
ClickHandlerPtr _interactionLink;
QImage _selectedFrame;
int _singleSize = 0;
int _animationsCheckVersion = -1;
ChatHelpers::StickerLottieSize _cachingTag = {};
bool _hasHeavyPart = false;
bool _resolving = false;

View file

@ -23,7 +23,7 @@ namespace {
not_null<Element*> view,
const QString &emoji,
int value) {
const auto &session = view->data()->history()->session();
const auto &session = view->history()->session();
return session.diceStickersPacks().lookup(emoji, value);
}

View file

@ -56,7 +56,7 @@ LargeEmoji::LargeEmoji(
const Ui::Text::IsolatedEmoji &emoji)
: _parent(parent)
, _images(ResolveImages(
&parent->data()->history()->session(),
&parent->history()->session(),
[=] { parent->customEmojiRepaint(); },
emoji)) {
}

View file

@ -67,7 +67,7 @@ MediaGift::MediaGift(
height);
const auto from = _gift->from();
const auto to = _parent->data()->history()->peer;
const auto to = _parent->history()->peer;
const auto months = _gift->months();
result.link = std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
@ -225,7 +225,7 @@ void MediaGift::ensureStickerCreated() const {
if (_sticker) {
return;
}
const auto &session = _parent->data()->history()->session();
const auto &session = _parent->history()->session();
auto &packs = session.giftBoxStickersPacks();
if (const auto document = packs.lookup(_gift->months())) {
if (const auto sticker = document->sticker()) {

View file

@ -37,7 +37,7 @@ const auto &kEmoji = ::Stickers::DicePacks::kSlotString;
[[nodiscard]] DocumentData *Lookup(
not_null<Element*> view,
int value) {
const auto &session = view->data()->history()->session();
const auto &session = view->history()->session();
return session.diceStickersPacks().lookup(kEmoji, value);
}

View file

@ -310,7 +310,7 @@ bool Sticker::readyToDrawAnimationFrame() {
if (!_player && loaded && !waitingForPremium && sticker->isAnimated()) {
setupPlayer();
}
return (_player && _player->ready());
return ready();
}
QSize Sticker::Size() {
@ -362,6 +362,10 @@ ClickHandlerPtr Sticker::link() {
return _link;
}
bool Sticker::ready() const {
return _player && _player->ready();
}
DocumentData *Sticker::document() {
return _data;
}

View file

@ -70,6 +70,7 @@ public:
const QRect &r) override;
ClickHandlerPtr link() override;
[[nodiscard]] bool ready() const;
DocumentData *document() override;
void stickerClearLoopPlayed() override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(

View file

@ -218,7 +218,7 @@ struct ForwardedTooltip {
const auto phrase = tr::lng_forwarded(
tr::now,
lt_user,
view->data()->history()->session().user()->name);
view->history()->session().user()->name);
const auto kReplacementPosition = QChar(0x0001);
const auto possiblePosition = tr::lng_forwarded(
tr::now,