diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp index c968b7433..e706b5315 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp @@ -32,6 +32,7 @@ namespace { constexpr auto kMinDelay = crl::time(200); constexpr auto kAccumulateDelay = crl::time(1000); +constexpr auto kAccumulateSeenRequests = kAccumulateDelay; constexpr auto kMaxDelay = 2 * crl::time(1000); constexpr auto kTimeNever = std::numeric_limits::max(); constexpr auto kJsonVersion = 1; @@ -75,7 +76,8 @@ void EmojiInteractions::checkEdition( } } -void EmojiInteractions::startOutgoing(not_null view) { +void EmojiInteractions::startOutgoing( + not_null view) { const auto item = view->data(); if (!IsServerMsgId(item->id) || !item->history()->peer->isUser()) { return; @@ -163,6 +165,7 @@ void EmojiInteractions::startIncoming( .document = document, .media = media, .scheduledAt = at, + .incoming = true, .index = index, }); } @@ -206,9 +209,11 @@ auto EmojiInteractions::checkAnimations( } else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) { animation.startedAt = now; _playRequests.fire({ + animation.emoji->text(), item, animation.media, animation.scheduledAt, + animation.incoming, }); break; } else { @@ -309,16 +314,36 @@ void EmojiInteractions::check(crl::time now) { if (!now) { now = crl::now(); } + checkSeenRequests(now); const auto result1 = checkAnimations(now); const auto result2 = checkAccumulated(now); const auto result = Combine(result1, result2); if (result.nextCheckAt < kTimeNever) { Assert(result.nextCheckAt > now); _checkTimer.callOnce(result.nextCheckAt - now); + } else if (!_playStarted.empty()) { + _checkTimer.callOnce(kAccumulateSeenRequests); } setWaitingForDownload(result.waitingForDownload); } +void EmojiInteractions::checkSeenRequests(crl::time now) { + for (auto i = begin(_playStarted); i != end(_playStarted);) { + for (auto j = begin(i->second); j != end(i->second);) { + if (j->second + kAccumulateSeenRequests <= now) { + j = i->second.erase(j); + } else { + ++j; + } + } + if (i->second.empty()) { + i = _playStarted.erase(i); + } else { + ++i; + } + } +} + void EmojiInteractions::setWaitingForDownload(bool waiting) { if (_waitingForDownload == waiting) { return; @@ -335,6 +360,25 @@ void EmojiInteractions::setWaitingForDownload(bool waiting) { } } +void EmojiInteractions::playStarted(not_null peer, QString emoji) { + auto &map = _playStarted[peer]; + const auto i = map.find(emoji); + const auto now = crl::now(); + if (i != end(map) && now - i->second < kAccumulateSeenRequests) { + return; + } + _session->api().request(MTPmessages_SetTyping( + MTP_flags(0), + peer->input, + MTPint(), // top_msg_id + MTP_sendMessageEmojiInteractionSeen(MTP_string(emoji)) + )).send(); + map[emoji] = now; + if (!_checkTimer.isActive()) { + _checkTimer.callOnce(kAccumulateSeenRequests); + } +} + EmojiInteractionsBunch EmojiInteractions::Parse(const QByteArray &json) { auto error = QJsonParseError{ 0, QJsonParseError::NoError }; const auto document = QJsonDocument::fromJson(json, &error); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h index 9ae293cfd..60fcf3b1c 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h @@ -28,9 +28,11 @@ class Element; namespace ChatHelpers { struct EmojiInteractionPlayRequest { + QString emoji; not_null item; std::shared_ptr media; crl::time shouldHaveStartedAt = 0; + bool incoming = false; }; struct EmojiInteractionsBunch { @@ -58,6 +60,7 @@ public: [[nodiscard]] rpl::producer playRequests() const { return _playRequests.events(); } + void playStarted(not_null peer, QString emoji); [[nodiscard]] static EmojiInteractionsBunch Parse(const QByteArray &json); [[nodiscard]] static QByteArray ToJson( @@ -70,6 +73,7 @@ private: std::shared_ptr media; crl::time scheduledAt = 0; crl::time startedAt = 0; + bool incoming = false; int index = 0; }; struct CheckResult { @@ -93,6 +97,7 @@ private: std::vector &animations); void setWaitingForDownload(bool waiting); + void checkSeenRequests(crl::time now); void checkEdition( not_null item, base::flat_map, std::vector> &map); @@ -103,6 +108,9 @@ private: base::flat_map, std::vector> _incoming; base::Timer _checkTimer; rpl::event_stream _playRequests; + base::flat_map< + not_null, + base::flat_map> _playStarted; bool _waitingForDownload = false; rpl::lifetime _downloadCheckLifetime; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 3fb06b247..850e5d7db 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -212,6 +212,10 @@ HistoryInner::HistoryInner( ) | rpl::start_with_next([=](QRect rect) { update(rect.translated(0, _historyPaddingTop)); }, lifetime()); + _emojiInteractions->playStarted( + ) | rpl::start_with_next([=](QString &&emoji) { + _controller->emojiInteractions().playStarted(_peer, std::move(emoji)); + }, lifetime()); session().data().itemRemoved( ) | rpl::start_with_next( diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index a2a01d101..cb2fb1001 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -60,16 +60,29 @@ void EmojiInteractions::play( ChatHelpers::EmojiInteractionPlayRequest request, not_null view) { if (_plays.empty()) { - play(view, std::move(request.media)); + play( + std::move(request.emoji), + view, + std::move(request.media), + request.incoming); } else { - _delayed.push_back({ view, request.media, crl::now() }); + const auto now = crl::now(); + _delayed.push_back({ + request.emoji, + view, + std::move(request.media), + now, + request.incoming, + }); checkDelayed(); } } void EmojiInteractions::play( + QString emoji, not_null view, - std::shared_ptr media) { + std::shared_ptr media, + bool incoming) { const auto top = view->block()->y() + view->y(); const auto bottom = top + view->height(); if (_visibleTop >= bottom @@ -100,6 +113,9 @@ void EmojiInteractions::play( .lottie = std::move(lottie), .shift = shift, }); + if (incoming) { + _playStarted.fire(std::move(emoji)); + } if (const auto media = view->media()) { media->stickerClearLoopPlayed(); } @@ -193,11 +209,16 @@ void EmojiInteractions::checkDelayed() { } auto good = std::move(*i); _delayed.erase(begin(_delayed), i + 1); - play(good.view, std::move(good.media)); + const auto incoming = good.incoming; + play(std::move(good.emoji), good.view, std::move(good.media), incoming); } rpl::producer EmojiInteractions::updateRequests() const { return _updateRequests.events(); } +rpl::producer EmojiInteractions::playStarted() const { + return _playStarted.events(); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h index d45376bd5..6de853e2b 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h @@ -39,6 +39,7 @@ public: void paint(QPainter &p); [[nodiscard]] rpl::producer updateRequests() const; + [[nodiscard]] rpl::producer playStarted() const; private: struct Play { @@ -51,16 +52,20 @@ private: bool finished = false; }; struct Delayed { + QString emoji; not_null view; std::shared_ptr media; crl::time shouldHaveStartedAt = 0; + bool incoming = false; }; [[nodiscard]] QRect computeRect(not_null view) const; void play( + QString emoji, not_null view, - std::shared_ptr media); + std::shared_ptr media, + bool incoming); void checkDelayed(); const not_null _session; @@ -72,6 +77,7 @@ private: std::vector _plays; std::vector _delayed; rpl::event_stream _updateRequests; + rpl::event_stream _playStarted; rpl::lifetime _lifetime;