From 7e4c9f98a6c1da77d4f0cadef1360a55f7aeb279 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 1 Aug 2019 22:14:19 +0100 Subject: [PATCH] Track all single-emoji messages. --- .../chat_helpers/stickers_emoji_pack.cpp | 154 ++++++++++++++---- .../chat_helpers/stickers_emoji_pack.h | 11 ++ .../history/history_inner_widget.cpp | 2 +- Telegram/SourceFiles/history/history_item.h | 30 ++-- .../SourceFiles/history/history_message.cpp | 24 ++- .../SourceFiles/history/history_message.h | 7 +- Telegram/SourceFiles/mtproto/type_utils.h | 5 +- 7 files changed, 180 insertions(+), 53 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index 65d3ad149..f317b5444 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -23,20 +23,58 @@ constexpr auto kRefreshTimeout = TimeId(7200); EmojiPack::EmojiPack(not_null session) : _session(session) { refresh(); + + session->data().itemRemoved( + ) | rpl::filter([](not_null item) { + return item->isSingleEmoji(); + }) | rpl::start_with_next([=](not_null item) { + remove(item); + }, _lifetime); +} + +bool EmojiPack::add(not_null item, const QString &text) { + auto length = 0; + const auto trimmed = text.trimmed(); + if (const auto emoji = Ui::Emoji::Find(trimmed, &length)) { + if (length == trimmed.size()) { + _items[emoji].emplace(item); + return true; + } + } + return false; +} + +bool EmojiPack::remove(not_null item) { + if (!item->isSingleEmoji()) { + return false; + } + auto length = 0; + const auto trimmed = item->originalString().trimmed(); + const auto emoji = Ui::Emoji::Find(trimmed, &length); + Assert(emoji != nullptr); + Assert(length == trimmed.size()); + const auto i = _items.find(emoji); + Assert(i != end(_items)); + const auto j = i->second.find(item); + Assert(j != end(i->second)); + i->second.erase(j); + if (i->second.empty()) { + _items.erase(i); + } + return true; } DocumentData *EmojiPack::stickerForEmoji(not_null item) { - const auto text = item->originalText().text.trimmed(); - auto length = 0; - const auto emoji = Ui::Emoji::Find(text, &length); - if (!emoji || length != text.size()) { + if (!item->isSingleEmoji()) { return nullptr; } + auto length = 0; + const auto trimmed = item->originalString().trimmed(); + const auto emoji = Ui::Emoji::Find(trimmed, &length); + Assert(emoji != nullptr); + Assert(length == trimmed.size()); const auto i = _map.find(emoji); - if (i != end(_map)) { - return i->second; - } - return nullptr; + return (i != end(_map)) ? i->second.get() : nullptr; } void EmojiPack::refresh() { @@ -46,42 +84,86 @@ void EmojiPack::refresh() { _requestId = _session->api().request(MTPmessages_GetStickerSet( MTP_inputStickerSetAnimatedEmoji() )).done([=](const MTPmessages_StickerSet &result) { + _requestId = 0; refreshDelayed(); result.match([&](const MTPDmessages_stickerSet &data) { - auto map = base::flat_map>(); - for (const auto &sticker : data.vdocuments().v) { - const auto document = _session->data().processDocument( - sticker); - if (document->sticker()) { - map.emplace(document->id, document); - } - } - for (const auto &pack : data.vpacks().v) { - pack.match([&](const MTPDstickerPack &data) { - const auto emoji = [&] { - return Ui::Emoji::Find(qs(data.vemoticon())); - }(); - const auto document = [&]() -> DocumentData* { - for (const auto &id : data.vdocuments().v) { - const auto i = map.find(id.v); - if (i != end(map)) { - return i->second.get(); - } - } - return nullptr; - }(); - if (emoji && document) { - _map.emplace_or_assign(emoji, document); - } - }); - } + applySet(data); }); - int a = 0; }).fail([=](const RPCError &error) { + _requestId = 0; refreshDelayed(); }).send(); } +void EmojiPack::applySet(const MTPDmessages_stickerSet &data) { + const auto stickers = collectStickers(data.vdocuments().v); + auto was = base::take(_map); + + for (const auto &pack : data.vpacks().v) { + pack.match([&](const MTPDstickerPack &data) { + applyPack(data, stickers); + }); + } + + for (const auto &[emoji, document] : _map) { + const auto i = was.find(emoji); + if (i == end(was)) { + refreshItems(emoji); + } else { + if (i->second != document) { + refreshItems(i->first); + } + was.erase(i); + } + } + for (const auto &[emoji, Document] : was) { + refreshItems(emoji); + } +} + +void EmojiPack::refreshItems(EmojiPtr emoji) { + const auto i = _items.find(emoji); + if (i == end(_items)) { + return; + } + for (const auto &item : i->second) { + _session->data().requestItemViewRefresh(item); + } +} + +void EmojiPack::applyPack( + const MTPDstickerPack &data, + const base::flat_map> &map) { + const auto emoji = [&] { + return Ui::Emoji::Find(qs(data.vemoticon())); + }(); + const auto document = [&]() -> DocumentData * { + for (const auto &id : data.vdocuments().v) { + const auto i = map.find(id.v); + if (i != end(map)) { + return i->second.get(); + } + } + return nullptr; + }(); + if (emoji && document) { + _map.emplace_or_assign(emoji, document); + } +} + +base::flat_map> EmojiPack::collectStickers( + const QVector &list) const { + auto result = base::flat_map>(); + for (const auto &sticker : list) { + const auto document = _session->data().processDocument( + sticker); + if (document->sticker()) { + result.emplace(document->id, document); + } + } + return result; +} + void EmojiPack::refreshDelayed() { App::CallDelayed(kRefreshTimeout, _session, [=] { refresh(); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h index 6c31ad3bc..4d5efd130 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h @@ -20,15 +20,26 @@ class EmojiPack final { public: explicit EmojiPack(not_null session); + bool add(not_null item, const QString &text); + bool remove(not_null item); + [[nodiscard]] DocumentData *stickerForEmoji(not_null item); private: void refresh(); void refreshDelayed(); + void applySet(const MTPDmessages_stickerSet &data); + void applyPack( + const MTPDstickerPack &data, + const base::flat_map> &map); + base::flat_map> collectStickers( + const QVector &list) const; + void refreshItems(EmojiPtr emoji); not_null _session; base::flat_set> _notLoaded; base::flat_map> _map; + base::flat_map>> _items; mtpRequestId _requestId = 0; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index e2ad6742f..7dec8df67 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1680,7 +1680,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto media = (view ? view->media() : nullptr); const auto mediaHasTextForCopy = media && media->hasTextForCopy(); if (const auto document = media ? media->getDocument() : nullptr) { - if (document->sticker()) { + if (!item->isSingleEmoji() && document->sticker()) { if (document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { _menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] { showStickerPackInfo(document); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 555acf026..8a4ab4f93 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -139,19 +139,19 @@ public: return 0; } - bool definesReplyKeyboard() const; - MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const; + [[nodiscard]] bool definesReplyKeyboard() const; + [[nodiscard]] MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const; - bool hasSwitchInlineButton() const { + [[nodiscard]] bool hasSwitchInlineButton() const { return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button; } - bool hasTextLinks() const { + [[nodiscard]] bool hasTextLinks() const { return _flags & MTPDmessage_ClientFlag::f_has_text_links; } - bool isGroupEssential() const { + [[nodiscard]] bool isGroupEssential() const { return _flags & MTPDmessage_ClientFlag::f_is_group_essential; } - bool isLocalUpdateMedia() const { + [[nodiscard]] bool isLocalUpdateMedia() const { return _flags & MTPDmessage_ClientFlag::f_is_local_update_media; } void setIsLocalUpdateMedia(bool flag) { @@ -161,22 +161,25 @@ public: _flags &= ~MTPDmessage_ClientFlag::f_is_local_update_media; } } - bool isGroupMigrate() const { + [[nodiscard]] bool isGroupMigrate() const { return isGroupEssential() && isEmpty(); } - bool hasViews() const { + [[nodiscard]] bool isSingleEmoji() const { + return _flags & MTPDmessage_ClientFlag::f_single_emoji; + } + [[nodiscard]] bool hasViews() const { return _flags & MTPDmessage::Flag::f_views; } - bool isPost() const { + [[nodiscard]] bool isPost() const { return _flags & MTPDmessage::Flag::f_post; } - bool isSilent() const { + [[nodiscard]] bool isSilent() const { return _flags & MTPDmessage::Flag::f_silent; } - bool isSending() const { + [[nodiscard]] bool isSending() const { return _flags & MTPDmessage_ClientFlag::f_sending; } - bool hasFailed() const { + [[nodiscard]] bool hasFailed() const { return _flags & MTPDmessage_ClientFlag::f_failed; } void sendFailed(); @@ -224,6 +227,9 @@ public: virtual QString inReplyText() const { return inDialogsText(DrawInDialog::WithoutSender); } + virtual QString originalString() const { + return QString(); + } virtual TextWithEntities originalText() const { return TextWithEntities(); } diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 6b8deb6f8..bf103bf11 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_service.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_context_menu.h" // For CopyPostLink(). +#include "chat_helpers/stickers_emoji_pack.h" #include "main/main_session.h" #include "boxes/share_box.h" #include "boxes/confirm_box.h" @@ -1083,6 +1084,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) { } } + clearSingleEmoji(); if (_media && _media->consumeMessageText(textWithEntities)) { setEmptyText(); } else { @@ -1095,8 +1097,10 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) { // just replace it with something so that UI won't look buggy. _text.setMarkedText( st::messageTextStyle, - { QString::fromUtf8("\xF0\x9F\x98\x94"), EntitiesInText() }, + { QString::fromUtf8(":-("), EntitiesInText() }, Ui::ItemTextOptions(this)); + } else if (!_media) { + checkSingleEmoji(textWithEntities.text); } _textWidth = -1; _textHeight = 0; @@ -1113,6 +1117,20 @@ void HistoryMessage::setEmptyText() { _textHeight = 0; } +void HistoryMessage::clearSingleEmoji() { + if (!(_flags & MTPDmessage_ClientFlag::f_single_emoji)) { + return; + } + history()->session().emojiStickersPack().remove(this); + _flags &= ~MTPDmessage_ClientFlag::f_single_emoji; +} + +void HistoryMessage::checkSingleEmoji(const QString &text) { + if (history()->session().emojiStickersPack().add(this, text)) { + _flags |= MTPDmessage_ClientFlag::f_single_emoji; + } +} + void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { if (!markup) { if (_flags & MTPDmessage::Flag::f_reply_markup) { @@ -1155,6 +1173,10 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { } } +QString HistoryMessage::originalString() const { + return emptyText() ? QString() : _text.toString(); +} + TextWithEntities HistoryMessage::originalText() const { if (emptyText()) { return { QString(), EntitiesInText() }; diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 9f4f732f5..f692baad5 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -29,8 +29,7 @@ QString GetErrorTextForForward( bool ignoreSlowmodeCountdown = false); void FastShareMessage(not_null item); -class HistoryMessage - : public HistoryItem { +class HistoryMessage : public HistoryItem { public: HistoryMessage( not_null history, @@ -134,6 +133,7 @@ public: [[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override; void setText(const TextWithEntities &textWithEntities) override; + [[nodiscard]] QString originalString() const override; [[nodiscard]] TextWithEntities originalText() const override; [[nodiscard]] TextForMimeData clipboardText() const override; [[nodiscard]] bool textHasLinks() const override; @@ -164,6 +164,9 @@ private: return _flags & MTPDmessage::Flag::f_legacy; } + void clearSingleEmoji(); + void checkSingleEmoji(const QString &text); + // For an invoice button we replace the button text with a "Receipt" key. // It should show the receipt for the payed invoice. Still let mobile apps do that. void replaceBuyWithReceiptInMarkup(); diff --git a/Telegram/SourceFiles/mtproto/type_utils.h b/Telegram/SourceFiles/mtproto/type_utils.h index 924728363..8d01f3f84 100644 --- a/Telegram/SourceFiles/mtproto/type_utils.h +++ b/Telegram/SourceFiles/mtproto/type_utils.h @@ -63,8 +63,11 @@ enum class MTPDmessage_ClientFlag : uint32 { // message was an outgoing message and failed to be sent f_failed = (1U << 22), + // message has no media and only a single emoji text + f_single_emoji = (1U << 21), + // update this when adding new client side flags - MIN_FIELD = (1U << 22), + MIN_FIELD = (1U << 21), }; DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)