diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index 35945d979..74a64a25f 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -165,7 +165,14 @@ bool RepliesList::buildFromData(not_null viewer) { = 0; return true; } - const auto around = viewer->around; + const auto around = [&] { + if (viewer->around != ShowAtUnreadMsgId) { + return viewer->around; + } else if (const auto item = lookupRoot()) { + return item->repliesReadTill(); + } + return viewer->around; + }(); if (_list.empty() || (!around && _skippedAfter != 0) || (around > _list.front() && _skippedAfter != 0) @@ -251,6 +258,10 @@ Histories &RepliesList::histories() { return _history->owner().histories(); } +HistoryItem *RepliesList::lookupRoot() { + return _history->owner().message(_history->channelId(), _rootId); +} + void RepliesList::loadAround(MsgId id) { if (_loadingAround && *_loadingAround == id) { return; diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index cd0de887f..b9108a52e 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -33,6 +33,7 @@ public: private: struct Viewer; + HistoryItem *lookupRoot(); [[nodiscard]] Histories &histories(); [[nodiscard]] rpl::producer sourceFromServer( diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index ae6508e9c..a903cc45b 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -264,6 +264,11 @@ public: } virtual void changeRepliesCount(int delta, PeerId replier) { } + virtual void setRepliesReadTill(MsgId readTillId) { + } + [[nodiscard]] virtual MsgId repliesReadTill() const { + return MsgId(0); + } virtual void setReplyToTop(MsgId replyToTop) { } virtual void setRealId(MsgId newId); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index ec5bb6793..2125c9f14 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -46,6 +46,7 @@ struct HistoryMessageViews : public RuntimeComponent { diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 640d480ea..eab50fab5 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1561,6 +1561,22 @@ void HistoryMessage::changeRepliesCount(int delta, PeerId replier) { refreshRepliesText(views); } +void HistoryMessage::setRepliesReadTill(MsgId readTillId) { + auto views = Get(); + if (!views) { + AddComponents(HistoryMessageViews::Bit()); + views = Get(); + } + views->repliesReadTillId = std::max(readTillId, 1); +} + +MsgId HistoryMessage::repliesReadTill() const { + if (const auto views = Get()) { + return views->repliesReadTillId; + } + return 0; +} + void HistoryMessage::setReplyToTop(MsgId replyToTop) { const auto reply = Get(); if (!reply diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index cad3ef2da..477bd9bba 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -136,6 +136,8 @@ public: void setForwardsCount(int count) override; void setReplies(const MTPMessageReplies &data) override; void changeRepliesCount(int delta, PeerId replier) override; + void setRepliesReadTill(MsgId readTillId) override; + MsgId repliesReadTill() const override; void setReplyToTop(MsgId replyToTop) override; void setRealId(MsgId newId) override; void incrementReplyToTopCounter() override; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 4b046fe68..3be3bf18c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1265,8 +1265,8 @@ void ListWidget::restoreState(not_null memento) { _aroundIndex = -1; if (const auto limit = memento->idsLimit()) { _idsLimit = limit; - _scrollTopState = memento->scrollTopState(); } + _scrollTopState = memento->scrollTopState(); refreshViewer(); } @@ -2663,6 +2663,10 @@ void ListWidget::replyToMessageRequestNotify(FullMsgId item) { _requestedToReplyToMessage.fire(std::move(item)); } +rpl::producer ListWidget::readMessageRequested() const { + return _requestedToReadMessage.events(); +} + ListWidget::~ListWidget() = default; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 284fe1197..1d76f4d0c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -188,10 +188,11 @@ public: QPoint tooltipPos() const override; bool tooltipWindowActive() const override; - rpl::producer editMessageRequested() const; + [[nodiscard]] rpl::producer editMessageRequested() const; void editMessageRequestNotify(FullMsgId item); - rpl::producer replyToMessageRequested() const; + [[nodiscard]] rpl::producer replyToMessageRequested() const; void replyToMessageRequestNotify(FullMsgId item); + [[nodiscard]] rpl::producer readMessageRequested() const; // ElementDelegate interface. Context elementContext() override; @@ -529,6 +530,7 @@ private: rpl::event_stream _requestedToEditMessage; rpl::event_stream _requestedToReplyToMessage; + rpl::event_stream _requestedToReadMessage; rpl::lifetime _viewerLifetime; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index bcebdd639..6dc7d8ace 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -59,6 +59,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +constexpr auto kReadRequestTimeout = 3 * crl::time(1000); + void ShowErrorToast(const QString &text) { Ui::Toast::Show(Ui::Toast::Config{ .text = { text }, @@ -80,6 +82,16 @@ bool CanSendFiles(not_null data) { } // namespace +RepliesMemento::RepliesMemento(not_null commentsItem) +: RepliesMemento(commentsItem->history(), commentsItem->id) { + if (commentsItem->repliesReadTill() == MsgId(1)) { + _list.setAroundPosition(Data::MinMessagePosition); + _list.setScrollTopState(ListMemento::ScrollTopState{ + Data::MinMessagePosition + }); + } +} + object_ptr RepliesMemento::createWidget( QWidget *parent, not_null controller, @@ -106,6 +118,7 @@ RepliesWidget::RepliesWidget( , _history(history) , _rootId(rootId) , _root(lookupRoot()) +, _commentsRoot(lookupCommentsRoot()) , _areComments(computeAreComments()) , _scroll(this, st::historyScroll, false) , _topBar(this, controller) @@ -115,7 +128,8 @@ RepliesWidget::RepliesWidget( controller, ComposeControls::Mode::Normal)) , _rootShadow(this) -, _scrollDown(_scroll, st::historyToDown) { +, _scrollDown(_scroll, st::historyToDown) +, _readRequestTimer([=] { sendReadTillRequest(); }) { setupRoot(); _rootHeight = st::msgReplyPadding.top() @@ -178,9 +192,13 @@ RepliesWidget::RepliesWidget( _history->session().changes().messageUpdates( Data::MessageUpdate::Flag::Destroyed - ) | rpl::filter([=](const Data::MessageUpdate &update) { - return (update.item == _replyReturn); - }) | rpl::start_with_next([=](const Data::MessageUpdate &update) { + ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { + if (update.item == _root) { + _root = nullptr; + } + if (update.item == _commentsRoot) { + _commentsRoot = nullptr; + } while (update.item == _replyReturn) { calculateNextReplyReturn(); } @@ -190,10 +208,33 @@ RepliesWidget::RepliesWidget( setupComposeControls(); } -RepliesWidget::~RepliesWidget() = default; +RepliesWidget::~RepliesWidget() { + if (_readRequestTimer.isActive()) { + sendReadTillRequest(); + } +} + +void RepliesWidget::sendReadTillRequest() { + if (!_commentsRoot || !_root) { + return; + } + if (_readRequestTimer.isActive()) { + _readRequestTimer.cancel(); + } + const auto api = &_history->session().api(); + api->request(base::take(_readRequestId)).cancel(); + _readRequestId = api->request(MTPmessages_ReadDiscussion( + _commentsRoot->history()->peer->input, + MTP_int(_commentsRoot->id), + MTP_int(_root->repliesReadTill()) + )).done([=](const MTPBool &) { + + }).send(); +} void RepliesWidget::setupRoot() { if (_root) { + setupCommentsRoot(); refreshRootView(); } else { const auto channel = _history->peer->asChannel(); @@ -201,6 +242,7 @@ void RepliesWidget::setupRoot() { _root = lookupRoot(); if (_root) { _areComments = computeAreComments(); + setupCommentsRoot(); } refreshRootView(); }); @@ -208,6 +250,30 @@ void RepliesWidget::setupRoot() { } } +void RepliesWidget::setupCommentsRoot() { + Expects(_root != nullptr); + + const auto postChannel = _root->discussionPostOriginalSender(); + if (!postChannel) { + return; + } else if (_commentsRoot) { + sendReadTillRequest(); + } else { + const auto forwarded = _root->Get(); + const auto messageId = forwarded->savedFromMsgId; + const auto done = crl::guard(this, [=](ChannelData*, MsgId) { + _commentsRoot = lookupCommentsRoot(); + if (_commentsRoot) { + sendReadTillRequest(); + } + }); + _history->session().api().requestMessageData( + postChannel, + messageId, + done); + } +} + void RepliesWidget::refreshRootView() { const auto sender = (_root && _root->discussionPostOriginalSender()) ? _root->discussionPostOriginalSender() @@ -240,6 +306,17 @@ HistoryItem *RepliesWidget::lookupRoot() const { return _history->owner().message(_history->channelId(), _rootId); } +HistoryItem *RepliesWidget::lookupCommentsRoot() const { + if (!computeAreComments()) { + return nullptr; + } + const auto forwarded = _root->Get(); + Assert(forwarded != nullptr); + return _history->owner().message( + forwarded->savedFromPeer->asChannel(), + forwarded->savedFromMsgId); +} + bool RepliesWidget::computeAreComments() const { return _root && _root->isDiscussionPost(); } @@ -1364,11 +1441,49 @@ void RepliesWidget::listSelectionChanged(SelectedItems &&items) { _topBar->showSelected(state); } +void RepliesWidget::readTill(MsgId tillId) { + if (!_root) { + return; + } + const auto now = _root->repliesReadTill(); + if (now < tillId) { + _root->setRepliesReadTill(tillId); + if (!_readRequestTimer.isActive()) { + _readRequestTimer.callOnce(kReadRequestTimeout); + } + } +} + void RepliesWidget::listVisibleItemsChanged(HistoryItemsList &&items) { + const auto reversed = ranges::view::reverse(items); + const auto good = ranges::find_if(reversed, [](auto item) { + return IsServerMsgId(item->id); + }); + if (good != end(reversed)) { + readTill((*good)->id); + } } std::optional RepliesWidget::listUnreadBarView( const std::vector> &elements) { + if (!_root) { + return std::nullopt; + } + const auto till = _root->repliesReadTill(); + if (till < 2) { + return std::nullopt; + } + for (auto i = 0, count = int(elements.size()); i != count; ++i) { + const auto item = elements[i]->data(); + if (item->id > till) { + if (item->out()) { + _root->setRepliesReadTill(item->id); + _readRequestTimer.callOnce(kReadRequestTimeout); + } else { + return i; + } + } + } return std::nullopt; } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 43a3f13e7..4dce4bff1 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_memento.h" #include "history/view/history_view_list_widget.h" #include "data/data_messages.h" +#include "base/timer.h" class History; enum class CompressConfirm; @@ -148,8 +149,11 @@ private: void setupComposeControls(); void setupRoot(); + void setupCommentsRoot(); void refreshRootView(); void setupDragArea(); + void sendReadTillRequest(); + void readTill(MsgId id); void setupScrollDownButton(); void scrollDownClicked(); @@ -172,6 +176,7 @@ private: [[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] MsgId replyToId() const; [[nodiscard]] HistoryItem *lookupRoot() const; + [[nodiscard]] HistoryItem *lookupCommentsRoot() const; [[nodiscard]] bool computeAreComments() const; void pushReplyReturn(not_null item); @@ -224,6 +229,7 @@ private: const not_null _history; const MsgId _rootId = 0; HistoryItem *_root = nullptr; + HistoryItem *_commentsRoot = nullptr; std::shared_ptr _replies; rpl::variable _areComments = false; object_ptr _scroll; @@ -248,14 +254,19 @@ private: Data::MessagesSlice _lastSlice; bool _choosingAttach = false; + base::Timer _readRequestTimer; + mtpRequestId _readRequestId = 0; + }; + class RepliesMemento : public Window::SectionMemento { public: RepliesMemento(not_null history, MsgId rootId) : _history(history) , _rootId(rootId) { } + explicit RepliesMemento(not_null commentsItem); object_ptr createWidget( QWidget *parent, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index ecec3f49d..dc371b3a7 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -110,7 +110,7 @@ void SessionNavigation::showRepliesForMessage( } else if (const auto id = item->commentsItemId()) { if (const auto item = _session->data().message(id)) { showSection( - HistoryView::RepliesMemento(item->history(), item->id)); + HistoryView::RepliesMemento(item)); return; } } @@ -148,8 +148,11 @@ void SessionNavigation::showRepliesForMessage( if (post) { post->setCommentsItemId(item->fullId()); } + if (const auto readTill = data.vread_max_id()) { + item->setRepliesReadTill(readTill->v); + } showSection( - HistoryView::RepliesMemento(item->history(), item->id)); + HistoryView::RepliesMemento(item)); } }); }).fail([=](const RPCError &error) {