diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 9b6f12d76..4ba708a4a 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2246,40 +2246,34 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateReadChannelDiscussionInbox: { const auto &d = update.c_updateReadChannelDiscussionInbox(); - const auto peerId = peerFromChannel(d.vchannel_id()); - const auto msgId = d.vtop_msg_id().v; + const auto id = FullMsgId( + peerFromChannel(d.vchannel_id()), + d.vtop_msg_id().v); const auto readTillId = d.vread_max_id().v; - const auto item = session().data().message(peerId, msgId); - const auto unreadCount = item - ? session().data().countUnreadRepliesLocally(item, readTillId) - : std::nullopt; + session().data().updateRepliesReadTill({ id, readTillId, false }); + const auto item = session().data().message(id); if (item) { - item->setRepliesInboxReadTill(readTillId, unreadCount); + item->setCommentsInboxReadTill(readTillId); if (const auto post = item->lookupDiscussionPostOriginal()) { - post->setRepliesInboxReadTill(readTillId, unreadCount); + post->setCommentsInboxReadTill(readTillId); } } if (const auto broadcastId = d.vbroadcast_id()) { if (const auto post = session().data().message( peerFromChannel(*broadcastId), d.vbroadcast_post()->v)) { - post->setRepliesInboxReadTill(readTillId, unreadCount); + post->setCommentsInboxReadTill(readTillId); } } } break; case mtpc_updateReadChannelDiscussionOutbox: { const auto &d = update.c_updateReadChannelDiscussionOutbox(); - const auto peerId = peerFromChannel(d.vchannel_id()); - const auto msgId = d.vtop_msg_id().v; + const auto id = FullMsgId( + peerFromChannel(d.vchannel_id()), + d.vtop_msg_id().v); const auto readTillId = d.vread_max_id().v; - const auto item = session().data().message(peerId, msgId); - if (item) { - item->setRepliesOutboxReadTill(readTillId); - if (const auto post = item->lookupDiscussionPostOriginal()) { - post->setRepliesOutboxReadTill(readTillId); - } - } + session().data().updateRepliesReadTill({ id, readTillId, true }); } break; case mtpc_updateChannelAvailableMessages: { diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index d72a0fd3d..f0a2f5290 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -151,7 +151,7 @@ struct MessageUpdate { ReplyMarkup = (1U << 5), BotCallbackSent = (1U << 6), NewMaybeAdded = (1U << 7), - RepliesUnreadCount = (1U << 8), + ReplyToTopAdded = (1U << 8), NewUnreadReaction = (1U << 9), LastUsedBit = (1U << 9), diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index f1f5f1382..88dc6196e 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_forum.h" #include "data/data_histories.h" +#include "data/data_replies_list.h" #include "data/data_session.h" #include "data/stickers/data_custom_emoji.h" #include "dialogs/dialogs_main_list.h" @@ -129,11 +130,27 @@ ForumTopic::ForumTopic(not_null history, MsgId rootId) : Entry(&history->owner(), Type::ForumTopic) , _history(history) , _list(forum()->topicsList()) +, _replies(std::make_shared(history, rootId)) , _rootId(rootId) { + _replies->unreadCountValue( + ) | rpl::combine_previous( + ) | rpl::filter([=] { + return inChatList(); + }) | rpl::start_with_next([=]( + std::optional previous, + std::optional now) { + notifyUnreadStateChange(unreadStateFor( + previous.value_or(0), + previous.has_value())); + }, _replies->lifetime()); } ForumTopic::~ForumTopic() = default; +std::shared_ptr ForumTopic::replies() const { + return _replies; +} + not_null ForumTopic::channel() const { return _history->peer->asChannel(); } @@ -151,7 +168,10 @@ MsgId ForumTopic::rootId() const { } void ForumTopic::setRealRootId(MsgId realId) { - _rootId = realId; + if (_rootId != realId) { + _rootId = realId; + _replies = std::make_shared(_history, _rootId); + } } void ForumTopic::applyTopic(const MTPForumTopic &topic) { @@ -175,10 +195,10 @@ void ForumTopic::applyTopic(const MTPForumTopic &topic) { } #endif - applyTopicFields( - data.vunread_count().v, + _replies->setInboxReadTill( data.vread_inbox_max_id().v, - data.vread_outbox_max_id().v); + data.vunread_count().v); + _replies->setOutboxReadTill(data.vread_outbox_max_id().v); applyTopicTopMessage(data.vtop_message().v); #if 0 // #TODO forum unread mark setUnreadMark(data.is_unread_mark()); @@ -215,17 +235,6 @@ int ForumTopic::chatListNameVersion() const { return _titleVersion; } -void ForumTopic::applyTopicFields( - int unreadCount, - MsgId maxInboxRead, - MsgId maxOutboxRead) { - if (maxInboxRead + 1 >= _inboxReadBefore.value_or(1)) { - setUnreadCount(unreadCount); - setInboxReadTill(maxInboxRead); - } - setOutboxReadTill(maxOutboxRead); -} - void ForumTopic::applyTopicTopMessage(MsgId topMessageId) { if (topMessageId) { const auto itemId = FullMsgId(_history->peer->id, topMessageId); @@ -295,22 +304,6 @@ void ForumTopic::setChatListMessage(HistoryItem *item) { } } -void ForumTopic::setInboxReadTill(MsgId upTo) { - if (_inboxReadBefore) { - accumulate_max(*_inboxReadBefore, upTo + 1); - } else { - _inboxReadBefore = upTo + 1; - } -} - -void ForumTopic::setOutboxReadTill(MsgId upTo) { - if (_outboxReadBefore) { - accumulate_max(*_outboxReadBefore, upTo + 1); - } else { - _outboxReadBefore = upTo + 1; - } -} - void ForumTopic::loadUserpic() { if (_icon) { [[maybe_unused]] const auto preload = _icon->ready(); @@ -464,7 +457,7 @@ void ForumTopic::applyItemRemoved(MsgId id) { } int ForumTopic::unreadCount() const { - return _unreadCount ? *_unreadCount : 0; + return _replies->unreadCountCurrent(); } int ForumTopic::unreadCountForBadge() const { @@ -473,16 +466,7 @@ int ForumTopic::unreadCountForBadge() const { } bool ForumTopic::unreadCountKnown() const { - return _unreadCount.has_value(); -} - -void ForumTopic::setUnreadCount(int newUnreadCount) { - if (_unreadCount == newUnreadCount) { - return; - } - const auto wasForBadge = (unreadCountForBadge() > 0); - const auto notifier = unreadStateChangeNotifier(true); - _unreadCount = newUnreadCount; + return _replies->unreadCountKnown(); } void ForumTopic::setUnreadMark(bool unread) { @@ -512,8 +496,13 @@ int ForumTopic::chatListUnreadCount() const { } Dialogs::UnreadState ForumTopic::chatListUnreadState() const { + return unreadStateFor(unreadCount(), unreadCountKnown()); +} + +Dialogs::UnreadState ForumTopic::unreadStateFor( + int count, + bool known) const { auto result = Dialogs::UnreadState(); - const auto count = _unreadCount.value_or(0); const auto mark = !count && _unreadMark; const auto muted = _history->mute(); result.messages = count; @@ -522,7 +511,7 @@ Dialogs::UnreadState ForumTopic::chatListUnreadState() const { result.chatsMuted = (count && muted) ? 1 : 0; result.marks = mark ? 1 : 0; result.marksMuted = (mark && muted) ? 1 : 0; - result.known = _unreadCount.has_value(); + result.known = known; return result; } diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 43333dcdb..3cb0af98d 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -26,6 +26,7 @@ class Session; namespace Data { +class RepliesList; class Session; class Forum; @@ -49,6 +50,7 @@ public: ForumTopic(const ForumTopic &) = delete; ForumTopic &operator=(const ForumTopic &) = delete; + [[nodiscard]] std::shared_ptr replies() const; [[nodiscard]] not_null channel() const; [[nodiscard]] not_null history() const; [[nodiscard]] not_null forum() const; @@ -102,7 +104,6 @@ public: [[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0. - void setUnreadCount(int newUnreadCount); void setUnreadMark(bool unread); [[nodiscard]] bool unreadMark() const; @@ -113,22 +114,20 @@ private: void indexTitleParts(); void validateDefaultIcon() const; void applyTopicTopMessage(MsgId topMessageId); - void applyTopicFields( - int unreadCount, - MsgId maxInboxRead, - MsgId maxOutboxRead); void setLastMessage(HistoryItem *item); void setLastServerMessage(HistoryItem *item); void setChatListMessage(HistoryItem *item); - void setInboxReadTill(MsgId upTo); - void setOutboxReadTill(MsgId upTo); - int chatListNameVersion() const override; + [[nodiscard]] Dialogs::UnreadState unreadStateFor( + int count, + bool known) const; + const not_null _history; const not_null _list; + std::shared_ptr _replies; MsgId _rootId = 0; QString _title; @@ -141,9 +140,6 @@ private: std::unique_ptr _icon; mutable QImage _defaultIcon; // on-demand - std::optional _inboxReadBefore; - std::optional _outboxReadBefore; - std::optional _unreadCount; std::optional _lastMessage; std::optional _lastServerMessage; std::optional _chatListMessage; diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index e1869a5ea..e06bd7474 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -60,6 +60,33 @@ RepliesList::RepliesList(not_null history, MsgId rootId) : _history(history) , _rootId(rootId) , _creating(IsCreating(history, rootId)) { + _history->owner().repliesReadTillUpdates( + ) | rpl::filter([=](const RepliesReadTillUpdate &update) { + return (update.id.msg == _rootId) + && (update.id.peer == _history->peer->id); + }) | rpl::start_with_next([=](const RepliesReadTillUpdate &update) { + if (update.out) { + setOutboxReadTill(update.readTillId); + } else if (update.readTillId >= _inboxReadTillId) { + setInboxReadTill( + update.readTillId, + computeUnreadCountLocally(update.readTillId)); + } + }, _lifetime); + + _history->session().changes().messageUpdates( + MessageUpdate::Flag::NewAdded + | MessageUpdate::Flag::NewMaybeAdded + | MessageUpdate::Flag::ReplyToTopAdded + | MessageUpdate::Flag::Destroyed + ) | rpl::filter([=](const MessageUpdate &update) { + return applyUpdate(update); + }) | rpl::to_empty | rpl::start_to_stream(_listChanges, _lifetime); + + _history->owner().channelDifferenceTooLong( + ) | rpl::filter([=](not_null channel) { + return applyDifferenceTooLong(channel); + }) | rpl::to_empty | rpl::start_to_stream(_listChanges, _lifetime); } RepliesList::~RepliesList() { @@ -95,33 +122,20 @@ rpl::producer RepliesList::source( viewer->limitBefore = limitBefore; viewer->limitAfter = limitAfter; - _history->session().changes().messageUpdates( - MessageUpdate::Flag::NewAdded - | MessageUpdate::Flag::NewMaybeAdded - | MessageUpdate::Flag::Destroyed - ) | rpl::filter([=](const MessageUpdate &update) { - return applyUpdate(viewer, update); - }) | rpl::start_with_next(pushDelayed, lifetime); - _history->session().changes().historyUpdates( _history, Data::HistoryUpdate::Flag::ClientSideMessages ) | rpl::start_with_next(pushDelayed, lifetime); - _partLoaded.events( - ) | rpl::start_with_next(pushDelayed, lifetime); - - _history->owner().channelDifferenceTooLong( - ) | rpl::filter([=](not_null channel) { - if (_creating - || _history->peer != channel - || !_skippedAfter.has_value()) { - return false; - } - _skippedAfter = std::nullopt; - return true; + _history->session().changes().messageUpdates( + MessageUpdate::Flag::Destroyed + ) | rpl::filter([=](const MessageUpdate &update) { + return applyItemDestroyed(viewer, update.item); }) | rpl::start_with_next(pushDelayed, lifetime); + _listChanges.events( + ) | rpl::start_with_next(pushDelayed, lifetime); + push(); return lifetime; }; @@ -190,62 +204,16 @@ rpl::producer RepliesList::fullCount() const { return _fullCount.value() | rpl::filter_optional(); } -std::optional RepliesList::fullUnreadCountAfter( - MsgId readTillId, - MsgId wasReadTillId, - std::optional wasUnreadCountAfter) const { - Expects(readTillId >= wasReadTillId); +bool RepliesList::unreadCountKnown() const { + return _unreadCount.current().has_value(); +} - readTillId = std::max(readTillId, _rootId); - wasReadTillId = std::max(wasReadTillId, _rootId); - const auto backLoaded = (_skippedBefore == 0); - const auto frontLoaded = (_skippedAfter == 0); - const auto fullLoaded = backLoaded && frontLoaded; - const auto allUnread = (readTillId == _rootId) - || (fullLoaded && _list.empty()); - const auto countIncoming = [&](auto from, auto till) { - auto &owner = _history->owner(); - const auto peerId = _history->peer->id; - auto count = 0; - for (auto i = from; i != till; ++i) { - if (!owner.message(peerId, *i)->out()) { - ++count; - } - } - return count; - }; - if (allUnread && fullLoaded) { - // Should not happen too often unless the list is empty. - return countIncoming(begin(_list), end(_list)); - } else if (frontLoaded && !_list.empty() && readTillId >= _list.front()) { - // Always "count by local data" if read till the end. - return 0; - } else if (wasReadTillId == readTillId) { - // Otherwise don't recount the same value over and over. - return wasUnreadCountAfter; - } else if (frontLoaded && !_list.empty() && readTillId >= _list.back()) { - // And count by local data if it is available and read-till changed. - return countIncoming( - begin(_list), - ranges::lower_bound(_list, readTillId, std::greater<>())); - } else if (_list.empty()) { - return std::nullopt; - } else if (wasUnreadCountAfter.has_value() - && (frontLoaded || readTillId <= _list.front()) - && (backLoaded || wasReadTillId >= _list.back())) { - // Count how many were read since previous value. - const auto from = ranges::lower_bound( - _list, - readTillId, - std::greater<>()); - const auto till = ranges::lower_bound( - from, - end(_list), - wasReadTillId, - std::greater<>()); - return std::max(*wasUnreadCountAfter - countIncoming(from, till), 0); - } - return std::nullopt; +int RepliesList::unreadCountCurrent() const { + return _unreadCount.current().value_or(0); +} + +rpl::producer> RepliesList::unreadCountValue() const { + return _unreadCount.value(); } void RepliesList::injectRootMessageAndReverse(not_null viewer) { @@ -321,7 +289,7 @@ bool RepliesList::buildFromData(not_null viewer) { if (viewer->around != ShowAtUnreadMsgId) { return viewer->around; } else if (const auto item = lookupRoot()) { - return item->computeRepliesInboxReadTillFull(); + return computeInboxReadTillFull(); } return viewer->around; }(); @@ -382,26 +350,36 @@ bool RepliesList::buildFromData(not_null viewer) { return true; } -bool RepliesList::applyUpdate( +bool RepliesList::applyItemDestroyed( not_null viewer, - const MessageUpdate &update) { - if (update.item->history() != _history || !update.item->isRegular()) { + not_null item) { + if (item->history() != _history || !item->isRegular()) { return false; } - if (update.flags & MessageUpdate::Flag::Destroyed) { - const auto id = update.item->fullId(); - for (auto i = 0; i != viewer->injectedForRoot; ++i) { - if (viewer->slice.ids[i] == id) { - return true; - } + const auto fullId = item->fullId(); + for (auto i = 0; i != viewer->injectedForRoot; ++i) { + if (viewer->slice.ids[i] == fullId) { + return true; } } - if (!update.item->inThread(_rootId)) { + return false; +} + +bool RepliesList::applyUpdate(const MessageUpdate &update) { + using Flag = MessageUpdate::Flag; + + if (update.item->history() != _history + || !update.item->isRegular() + || !update.item->inThread(_rootId)) { return false; } const auto id = update.item->id; + const auto added = (update.flags & Flag::ReplyToTopAdded); const auto i = ranges::lower_bound(_list, id, std::greater<>()); - if (update.flags & MessageUpdate::Flag::Destroyed) { + if (update.flags & Flag::Destroyed) { + if (!added) { + changeUnreadCountByPost(id, -1); + } if (i == end(_list) || *i != id) { return false; } @@ -413,22 +391,45 @@ bool RepliesList::applyUpdate( _fullCount = (*known - 1); } } - } else if (_skippedAfter != 0) { + return true; + } + if (added) { + changeUnreadCountByPost(id, 1); + } + if (_skippedAfter != 0 + || (i != end(_list) && *i == id)) { return false; - } else { - if (i != end(_list) && *i == id) { - return false; - } - _list.insert(i, id); - if (_skippedBefore && _skippedAfter) { - _fullCount = *_skippedBefore + _list.size() + *_skippedAfter; - } else if (const auto known = _fullCount.current()) { - _fullCount = *known + 1; - } + } + _list.insert(i, id); + if (_skippedBefore && _skippedAfter) { + _fullCount = *_skippedBefore + _list.size() + *_skippedAfter; + } else if (const auto known = _fullCount.current()) { + _fullCount = *known + 1; } return true; } +bool RepliesList::applyDifferenceTooLong(not_null channel) { + if (_creating + || _history->peer != channel + || !_skippedAfter.has_value()) { + return false; + } + _skippedAfter = std::nullopt; + return true; +} + +void RepliesList::changeUnreadCountByPost(MsgId id, int delta) { + if (!_inboxReadTillId) { + setUnreadCount(std::nullopt); + return; + } + const auto count = _unreadCount.current(); + if (count.has_value() && (id > _inboxReadTillId)) { + setUnreadCount(std::max(*count + delta, 0)); + } +} + Histories &RepliesList::histories() { return _history->owner().histories(); } @@ -479,6 +480,7 @@ void RepliesList::loadAround(MsgId id) { _skippedBefore = 0; } } + checkReadTillEnd(); }).fail([=] { _beforeId = 0; _loadingAround = std::nullopt; @@ -570,6 +572,7 @@ void RepliesList::loadAfter() { if (_skippedBefore == 0) { _fullCount = _list.size(); } + checkReadTillEnd(); } }).fail([=] { _afterId = 0; @@ -583,7 +586,7 @@ void RepliesList::loadAfter() { } bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { - const auto guard = gsl::finally([&] { _partLoaded.fire({}); }); + const auto guard = gsl::finally([&] { _listChanges.fire({}); }); const auto fullCount = result.match([&]( const MTPDmessages_messagesNotModified &) { @@ -674,17 +677,14 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { } _fullCount = checkedCount; + checkReadTillEnd(); + if (const auto item = lookupRoot()) { - if (_skippedAfter == 0 && !_list.empty()) { - item->setRepliesMaxId(_list.front()); - } else { - item->setRepliesPossibleMaxId(maxId); - } if (const auto original = item->lookupDiscussionPostOriginal()) { if (_skippedAfter == 0 && !_list.empty()) { - original->setRepliesMaxId(_list.front()); + original->setCommentsMaxId(_list.front()); } else { - original->setRepliesPossibleMaxId(maxId); + original->setCommentsPossibleMaxId(maxId); } } } @@ -693,4 +693,169 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { return (list.size() == skipped); } +void RepliesList::setInboxReadTill( + MsgId readTillId, + std::optional unreadCount) { + const auto newReadTillId = std::max(readTillId.bare, int64(1)); + const auto ignore = (newReadTillId < _inboxReadTillId); + if (ignore) { + return; + } + const auto changed = (newReadTillId > _inboxReadTillId); + if (changed) { + _inboxReadTillId = newReadTillId; + } + if (_skippedAfter == 0 + && !_list.empty() + && _inboxReadTillId >= _list.front()) { + unreadCount = 0; + } + const auto wasUnreadCount = _unreadCount; + if (_unreadCount.current() != unreadCount + && (changed || unreadCount.has_value())) { + setUnreadCount(unreadCount); + } +} + +MsgId RepliesList::inboxReadTillId() const { + return _inboxReadTillId; +} + +MsgId RepliesList::computeInboxReadTillFull() const { + const auto local = _inboxReadTillId; + if (const auto megagroup = _history->peer->asMegagroup()) { + if (!megagroup->isForum() && megagroup->amIn()) { + return std::max(local, _history->inboxReadTillId()); + } + } + return local; +} + +void RepliesList::setOutboxReadTill(MsgId readTillId) { + const auto newReadTillId = std::max(readTillId.bare, int64(1)); + if (newReadTillId > _outboxReadTillId) { + _outboxReadTillId = newReadTillId; + _history->session().changes().historyUpdated( + _history, + Data::HistoryUpdate::Flag::OutboxRead); + } +} + +MsgId RepliesList::computeOutboxReadTillFull() const { + const auto local = _outboxReadTillId; + if (const auto megagroup = _history->peer->asMegagroup()) { + if (!megagroup->isForum() && megagroup->amIn()) { + return std::max(local, _history->outboxReadTillId()); + } + } + return local; +} + +void RepliesList::setUnreadCount(std::optional count) { + _unreadCount = count; +} + +void RepliesList::checkReadTillEnd() { + if (_unreadCount.current() != 0 + && _skippedAfter == 0 + && !_list.empty() + && _inboxReadTillId >= _list.front()) { + setUnreadCount(0); + } +} + +std::optional RepliesList::computeUnreadCountLocally( + MsgId afterId) const { + Expects(afterId >= _inboxReadTillId); + + const auto wasUnreadCountAfter = _unreadCount.current(); + const auto readTillId = std::max(afterId, _rootId); + const auto wasReadTillId = std::max(_inboxReadTillId, _rootId); + const auto backLoaded = (_skippedBefore == 0); + const auto frontLoaded = (_skippedAfter == 0); + const auto fullLoaded = backLoaded && frontLoaded; + const auto allUnread = (readTillId == _rootId) + || (fullLoaded && _list.empty()); + const auto countIncoming = [&](auto from, auto till) { + auto &owner = _history->owner(); + const auto peerId = _history->peer->id; + auto count = 0; + for (auto i = from; i != till; ++i) { + if (!owner.message(peerId, *i)->out()) { + ++count; + } + } + return count; + }; + if (allUnread && fullLoaded) { + // Should not happen too often unless the list is empty. + return countIncoming(begin(_list), end(_list)); + } else if (frontLoaded && !_list.empty() && readTillId >= _list.front()) { + // Always "count by local data" if read till the end. + return 0; + } else if (wasReadTillId == readTillId) { + // Otherwise don't recount the same value over and over. + return wasUnreadCountAfter; + } else if (frontLoaded && !_list.empty() && readTillId >= _list.back()) { + // And count by local data if it is available and read-till changed. + return countIncoming( + begin(_list), + ranges::lower_bound(_list, readTillId, std::greater<>())); + } else if (_list.empty()) { + return std::nullopt; + } else if (wasUnreadCountAfter.has_value() + && (frontLoaded || readTillId <= _list.front()) + && (backLoaded || wasReadTillId >= _list.back())) { + // Count how many were read since previous value. + const auto from = ranges::lower_bound( + _list, + readTillId, + std::greater<>()); + const auto till = ranges::lower_bound( + from, + end(_list), + wasReadTillId, + std::greater<>()); + return std::max(*wasUnreadCountAfter - countIncoming(from, till), 0); + } + return std::nullopt; +} + +void RepliesList::requestUnreadCount() { + if (_reloadUnreadCountRequestId) { + return; + } + const auto weak = base::make_weak(this); + const auto session = &_history->session(); + const auto fullId = FullMsgId(_history->peer->id, _rootId); + const auto apply = [weak, session, fullId]( + int readTill, + int unreadCount) { + if (const auto strong = weak.get()) { + strong->setInboxReadTill(readTill, unreadCount); + } + if (const auto root = session->data().message(fullId)) { + if (const auto post = root->lookupDiscussionPostOriginal()) { + post->setCommentsInboxReadTill(readTill); + } + } + }; + _reloadUnreadCountRequestId = session->api().request( + MTPmessages_GetDiscussionMessage( + _history->peer->input, + MTP_int(_rootId)) + ).done([=](const MTPmessages_DiscussionMessage &result) { + if (weak) { + _reloadUnreadCountRequestId = 0; + } + result.match([&](const MTPDmessages_discussionMessage &data) { + session->data().processUsers(data.vusers()); + session->data().processChats(data.vchats()); + apply( + data.vread_inbox_max_id().value_or_empty(), + data.vunread_count().v); + }); + }).send(); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index 693897d43..4fcb5d4e1 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -31,10 +31,24 @@ public: [[nodiscard]] rpl::producer fullCount() const; - [[nodiscard]] std::optional fullUnreadCountAfter( - MsgId readTillId, - MsgId wasReadTillId, - std::optional wasUnreadCountAfter) const; + [[nodiscard]] bool unreadCountKnown() const; + [[nodiscard]] int unreadCountCurrent() const; + [[nodiscard]] rpl::producer> unreadCountValue() const; + + void setInboxReadTill(MsgId readTillId, std::optional unreadCount); + [[nodiscard]] MsgId inboxReadTillId() const; + [[nodiscard]] MsgId computeInboxReadTillFull() const; + + void setOutboxReadTill(MsgId readTillId); + [[nodiscard]] MsgId computeOutboxReadTillFull() const; + + [[nodiscard]] std::optional computeUnreadCountLocally( + MsgId afterId) const; + void requestUnreadCount(); + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } private: struct Viewer; @@ -49,9 +63,12 @@ private: void appendClientSideMessages(MessagesSlice &slice); [[nodiscard]] bool buildFromData(not_null viewer); - [[nodiscard]] bool applyUpdate( + [[nodiscard]] bool applyItemDestroyed( not_null viewer, - const MessageUpdate &update); + not_null item); + [[nodiscard]] bool applyUpdate(const MessageUpdate &update); + [[nodiscard]] bool applyDifferenceTooLong( + not_null channel); void injectRootMessageAndReverse(not_null viewer); void injectRootMessage(not_null viewer); void injectRootDivider( @@ -62,20 +79,32 @@ private: void loadBefore(); void loadAfter(); + void changeUnreadCountByPost(MsgId id, int delta); + void setUnreadCount(std::optional count); + void checkReadTillEnd(); + const not_null _history; const MsgId _rootId = 0; + const bool _creating = false; + std::vector _list; std::optional _skippedBefore; std::optional _skippedAfter; rpl::variable> _fullCount; - rpl::event_stream<> _partLoaded; + rpl::event_stream<> _listChanges; std::optional _loadingAround; + rpl::variable> _unreadCount; + MsgId _inboxReadTillId = 0; + MsgId _outboxReadTillId = 0; HistoryService *_divider = nullptr; bool _dividerWithComments = false; - bool _creating = false; int _beforeId = 0; int _afterId = 0; + mtpRequestId _reloadUnreadCountRequestId = 0; + + rpl::lifetime _lifetime; + }; } // namespace Data diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 5e6be8a52..ec51889d1 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2364,21 +2364,13 @@ void Session::notifyUnreadBadgeChanged() { _unreadBadgeChanges.fire({}); } -std::optional Session::countUnreadRepliesLocally( - not_null root, - MsgId afterId) const { - auto result = std::optional(); - _unreadRepliesCountRequests.fire({ - .root = root, - .afterId = afterId, - .result = &result, - }); - return result; +void Session::updateRepliesReadTill(RepliesReadTillUpdate update) { + _repliesReadTillUpdates.fire(std::move(update)); } -auto Session::unreadRepliesCountRequests() const --> rpl::producer { - return _unreadRepliesCountRequests.events(); +auto Session::repliesReadTillUpdates() const +-> rpl::producer { + return _repliesReadTillUpdates.events(); } int Session::computeUnreadBadge(const Dialogs::UnreadState &state) const { @@ -3787,6 +3779,14 @@ not_null Session::processFolder(const MTPDfolder &data) { return folder(data.vid().v); } +not_null Session::chatsListFor( + not_null entry) { + const auto topic = entry->asTopic(); + return topic + ? topic->forum()->topicsList() + : chatsList(entry->folder()); +} + not_null Session::chatsList(Data::Folder *folder) { return folder ? folder->chatsList().get() : &_chatsList; } @@ -3812,9 +3812,7 @@ void Session::refreshChatListEntry(Dialogs::Key key) { const auto entry = key.entry(); const auto history = entry->asHistory(); const auto topic = entry->asTopic(); - const auto mainList = topic - ? topic->forum()->topicsList() - : chatsList(entry->folder()); + const auto mainList = chatsListFor(entry); auto event = ChatListEntryRefresh{ .key = key }; const auto creating = event.existenceChanged = !entry->inChatList(); if (creating && topic && topic->forum()->creating(topic->rootId())) { @@ -3883,9 +3881,7 @@ void Session::removeChatListEntry(Dialogs::Key key) { }); } } - const auto mainList = entry->asTopic() - ? entry->asTopic()->forum()->topicsList() - : chatsList(entry->folder()); + const auto mainList = chatsListFor(entry); entry->removeFromChatList(0, mainList); _chatListEntryRefreshes.fire(ChatListEntryRefresh{ .key = key, diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 8b48115da..34d7f493d 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -65,6 +65,12 @@ class GroupCall; class NotifySettings; class CustomEmojiManager; +struct RepliesReadTillUpdate { + FullMsgId id; + MsgId readTillId; + bool out = false; +}; + class Session final { public: using ViewElement = HistoryView::Element; @@ -459,16 +465,9 @@ public: [[nodiscard]] rpl::producer<> unreadBadgeChanges() const; void notifyUnreadBadgeChanged(); - [[nodiscard]] std::optional countUnreadRepliesLocally( - not_null root, - MsgId afterId) const; - struct UnreadRepliesCountRequest { - not_null root; - MsgId afterId = 0; - not_null*> result; - }; - [[nodiscard]] auto unreadRepliesCountRequests() const - -> rpl::producer; + void updateRepliesReadTill(RepliesReadTillUpdate update); + [[nodiscard]] auto repliesReadTillUpdates() const + -> rpl::producer; void selfDestructIn(not_null item, crl::time delay); @@ -647,6 +646,8 @@ public: not_null processFolder(const MTPFolder &data); not_null processFolder(const MTPDfolder &data); + [[nodiscard]] not_null chatsListFor( + not_null entry); [[nodiscard]] not_null chatsList( Data::Folder *folder = nullptr); [[nodiscard]] not_null chatsList( @@ -863,7 +864,7 @@ private: rpl::event_stream _dialogsRowReplacements; rpl::event_stream _chatListEntryRefreshes; rpl::event_stream<> _unreadBadgeChanges; - rpl::event_stream _unreadRepliesCountRequests; + rpl::event_stream _repliesReadTillUpdates; Dialogs::MainList _chatsList; Dialogs::IndexedList _contactsList; diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 13c928f99..0b42bcd5b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -166,7 +166,7 @@ void Entry::notifyUnreadStateChange(const UnreadState &wasState) { Expects(inChatList()); const auto nowState = chatListUnreadState(); - owner().chatsList(folder())->unreadStateChanged(wasState, nowState); + owner().chatsListFor(this)->unreadStateChanged(wasState, nowState); auto &filters = owner().chatsFilters(); for (const auto &[filterId, links] : _chatListLinks) { filters.chatsList(filterId)->unreadStateChanged(wasState, nowState); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 8da7f3e12..03c8e3b02 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -235,29 +235,13 @@ public: } [[nodiscard]] bool hasExtendedMediaPreview() const; - [[nodiscard]] virtual MsgId repliesInboxReadTill() const { - return MsgId(0); + virtual void setCommentsInboxReadTill(MsgId readTillId) { } - virtual void setRepliesInboxReadTill( - MsgId readTillId, - std::optional unreadCount) { + virtual void setCommentsMaxId(MsgId maxId) { } - [[nodiscard]] virtual MsgId computeRepliesInboxReadTillFull() const { - return MsgId(0); + virtual void setCommentsPossibleMaxId(MsgId possibleMaxId) { } - [[nodiscard]] virtual MsgId repliesOutboxReadTill() const { - return MsgId(0); - } - virtual void setRepliesOutboxReadTill(MsgId readTillId) { - } - [[nodiscard]] virtual MsgId computeRepliesOutboxReadTillFull() const { - return MsgId(0); - } - virtual void setRepliesMaxId(MsgId maxId) { - } - virtual void setRepliesPossibleMaxId(MsgId possibleMaxId) { - } - [[nodiscard]] virtual bool areRepliesUnread() const { + [[nodiscard]] virtual bool areCommentsUnread() const { return false; } @@ -338,10 +322,7 @@ public: } virtual void clearReplies() { } - virtual void changeRepliesCount( - int delta, - PeerId replier, - std::optional unread) { + virtual void changeRepliesCount(int delta, PeerId replier) { } virtual void setReplyFields( MsgId replyTo, diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 4f78fbeae..0a3a8272a 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -58,12 +58,10 @@ struct HistoryMessageViews : public RuntimeComponent { diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index c776e7160..967aa3730 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -721,137 +721,67 @@ bool HistoryMessage::externalReply() const { return false; } -MsgId HistoryMessage::repliesInboxReadTill() const { - if (const auto views = Get()) { - return views->repliesInboxReadTillId; - } - return 0; -} - -void HistoryMessage::setRepliesInboxReadTill( - MsgId readTillId, - std::optional unreadCount) { - if (const auto views = Get()) { - const auto newReadTillId = std::max(readTillId.bare, int64(1)); - const auto ignore = (newReadTillId < views->repliesInboxReadTillId); - if (ignore) { - return; - } - const auto changed = (newReadTillId > views->repliesInboxReadTillId); - if (changed) { - const auto wasUnread = repliesAreComments() && areRepliesUnread(); - views->repliesInboxReadTillId = newReadTillId; - if (wasUnread && !areRepliesUnread()) { - history()->owner().requestItemRepaint(this); - } - } - const auto wasUnreadCount = (views->repliesUnreadCount >= 0) - ? std::make_optional(views->repliesUnreadCount) - : std::nullopt; - if (unreadCount != wasUnreadCount - && (changed || unreadCount.has_value())) { - setUnreadRepliesCount(views, unreadCount.value_or(-1)); - } - } -} - -MsgId HistoryMessage::computeRepliesInboxReadTillFull() const { +void HistoryMessage::setCommentsInboxReadTill(MsgId readTillId) { const auto views = Get(); if (!views) { - return 0; + return; } - const auto local = views->repliesInboxReadTillId; - const auto group = views->commentsMegagroupId - ? history()->owner().historyLoaded( - peerFromChannel(views->commentsMegagroupId)) - : history().get(); - if (const auto megagroup = group->peer->asChannel()) { - if (megagroup->amIn()) { - return std::max(local, group->inboxReadTillId()); - } + const auto newReadTillId = std::max(readTillId.bare, int64(1)); + const auto ignore = (newReadTillId < views->commentsInboxReadTillId); + if (ignore) { + return; + } + const auto changed = (newReadTillId > views->commentsInboxReadTillId); + if (!changed) { + return; + } + const auto wasUnread = areCommentsUnread(); + views->commentsInboxReadTillId = newReadTillId; + if (wasUnread && !areCommentsUnread()) { + history()->owner().requestItemRepaint(this); } - return local; } -MsgId HistoryMessage::repliesOutboxReadTill() const { +void HistoryMessage::setCommentsMaxId(MsgId maxId) { if (const auto views = Get()) { - return views->repliesOutboxReadTillId; - } - return 0; -} - -void HistoryMessage::setRepliesOutboxReadTill(MsgId readTillId) { - if (const auto views = Get()) { - const auto newReadTillId = std::max(readTillId.bare, int64(1)); - if (newReadTillId > views->repliesOutboxReadTillId) { - views->repliesOutboxReadTillId = newReadTillId; - if (!repliesAreComments()) { - history()->session().changes().historyUpdated( - history(), - Data::HistoryUpdate::Flag::OutboxRead); - } - } - } -} - -MsgId HistoryMessage::computeRepliesOutboxReadTillFull() const { - const auto views = Get(); - if (!views) { - return 0; - } - const auto local = views->repliesOutboxReadTillId; - const auto group = views->commentsMegagroupId - ? history()->owner().historyLoaded( - peerFromChannel(views->commentsMegagroupId)) - : history().get(); - if (const auto megagroup = group->peer->asChannel()) { - if (megagroup->amIn()) { - return std::max(local, group->outboxReadTillId()); - } - } - return local; -} - -void HistoryMessage::setRepliesMaxId(MsgId maxId) { - if (const auto views = Get()) { - if (views->repliesMaxId != maxId) { - const auto comments = repliesAreComments(); - const auto wasUnread = comments && areRepliesUnread(); - views->repliesMaxId = maxId; - if (comments && wasUnread != areRepliesUnread()) { + if (views->commentsMaxId != maxId) { + const auto wasUnread = areCommentsUnread(); + views->commentsMaxId = maxId; + if (wasUnread != areCommentsUnread()) { history()->owner().requestItemRepaint(this); } } } } -void HistoryMessage::setRepliesPossibleMaxId(MsgId possibleMaxId) { +void HistoryMessage::setCommentsPossibleMaxId(MsgId possibleMaxId) { if (const auto views = Get()) { - if (views->repliesMaxId < possibleMaxId) { - const auto comments = repliesAreComments(); - const auto wasUnread = comments && areRepliesUnread(); - views->repliesMaxId = possibleMaxId; - if (comments && !wasUnread && areRepliesUnread()) { + if (views->commentsMaxId < possibleMaxId) { + const auto wasUnread = areCommentsUnread(); + views->commentsMaxId = possibleMaxId; + if (!wasUnread && areCommentsUnread()) { history()->owner().requestItemRepaint(this); } } } } -bool HistoryMessage::areRepliesUnread() const { +bool HistoryMessage::areCommentsUnread() const { const auto views = Get(); - if (!views) { + if (!views + || !views->commentsMegagroupId + || !checkCommentsLinkedChat(views->commentsMegagroupId)) { return false; } - const auto local = views->repliesInboxReadTillId; - if (views->repliesInboxReadTillId < 2 || views->repliesMaxId <= local) { + const auto till = views->commentsInboxReadTillId; + if (views->commentsInboxReadTillId < 2 || views->commentsMaxId <= till) { return false; } const auto group = views->commentsMegagroupId ? history()->owner().historyLoaded( peerFromChannel(views->commentsMegagroupId)) : history().get(); - return !group || (views->repliesMaxId > group->inboxReadTillId()); + return !group || (views->commentsMaxId > group->inboxReadTillId()); } FullMsgId HistoryMessage::commentsItemId() const { @@ -1666,15 +1596,15 @@ void HistoryMessage::setReplies(HistoryMessageRepliesData &&data) { const auto channelId = data.channelId; const auto readTillId = data.readMaxId ? std::max({ - views->repliesInboxReadTillId.bare, + views->commentsInboxReadTillId.bare, data.readMaxId.bare, int64(1), }) - : views->repliesInboxReadTillId; - const auto maxId = data.maxId ? data.maxId : views->repliesMaxId; + : views->commentsInboxReadTillId; + const auto maxId = data.maxId ? data.maxId : views->commentsMaxId; const auto countsChanged = (views->replies.count != count) - || (views->repliesInboxReadTillId != readTillId) - || (views->repliesMaxId != maxId); + || (views->commentsInboxReadTillId != readTillId) + || (views->commentsMaxId != maxId); const auto megagroupChanged = (views->commentsMegagroupId != channelId); const auto recentChanged = (views->recentRepliers != repliers); if (!countsChanged && !megagroupChanged && !recentChanged) { @@ -1684,11 +1614,11 @@ void HistoryMessage::setReplies(HistoryMessageRepliesData &&data) { if (recentChanged) { views->recentRepliers = repliers; } + const auto wasUnread = areCommentsUnread(); views->commentsMegagroupId = channelId; - const auto wasUnread = channelId && areRepliesUnread(); - views->repliesInboxReadTillId = readTillId; - views->repliesMaxId = maxId; - if (channelId && wasUnread != areRepliesUnread()) { + views->commentsInboxReadTillId = readTillId; + views->commentsMaxId = maxId; + if (wasUnread != areCommentsUnread()) { history()->owner().requestItemRepaint(this); } refreshRepliesText(views, megagroupChanged); @@ -1740,25 +1670,13 @@ void HistoryMessage::refreshRepliesText( } } -void HistoryMessage::changeRepliesCount( - int delta, - PeerId replier, - std::optional unread) { +void HistoryMessage::changeRepliesCount(int delta, PeerId replier) { const auto views = Get(); const auto limit = HistoryMessageViews::kMaxRecentRepliers; if (!views) { return; } - // Update unread count. - if (!unread) { - setUnreadRepliesCount(views, -1); - } else if (views->repliesUnreadCount >= 0 && *unread) { - setUnreadRepliesCount( - views, - std::max(views->repliesUnreadCount + delta, 0)); - } - // Update full count. if (views->replies.count < 0) { return; @@ -1780,19 +1698,6 @@ void HistoryMessage::changeRepliesCount( history()->owner().notifyItemDataChange(this); } -void HistoryMessage::setUnreadRepliesCount( - not_null views, - int count) { - // Track unread count in discussion forwards, not in the channel posts. - if (views->repliesUnreadCount == count || views->commentsMegagroupId) { - return; - } - views->repliesUnreadCount = count; - history()->session().changes().messageUpdated( - this, - Data::MessageUpdate::Flag::RepliesUnreadCount); -} - void HistoryMessage::setSponsoredFrom(const Data::SponsoredFrom &from) { AddComponents(HistoryMessageSponsored::Bit()); const auto sponsored = Get(); @@ -1866,37 +1771,27 @@ void HistoryMessage::incrementReplyToTopCounter() { void HistoryMessage::changeReplyToTopCounter( not_null reply, int delta) { - if (!isRegular() || !reply->replyToTop()) { + if (!isRegular() || !_history->peer->isMegagroup()) { return; } - const auto peerId = _history->peer->id; - if (!peerIsChannel(peerId)) { + if (!out() && delta > 0) { + _history->session().changes().messageUpdated( + this, + Data::MessageUpdate::Flag::ReplyToTopAdded); + } + const auto topId = reply->replyToTop(); + if (!topId) { return; } - const auto top = _history->owner().message(peerId, reply->replyToTop()); + const auto top = _history->owner().message(_history->peer->id, topId); if (!top) { return; } - auto unread = out() ? std::make_optional(false) : std::nullopt; - if (const auto views = top->Get()) { - if (views->commentsMegagroupId) { - // This is a post in channel, we don't track its replies. - return; - } - if (views->repliesInboxReadTillId > 0) { - unread = !out() && (id > views->repliesInboxReadTillId); - } - } - const auto changeFor = [&](not_null item) { - if (const auto from = displayFrom()) { - item->changeRepliesCount(delta, from->id, unread); - } else { - item->changeRepliesCount(delta, PeerId(), unread); - } - }; - changeFor(top); + const auto from = displayFrom(); + const auto replier = from ? from->id : PeerId(); + top->changeRepliesCount(delta, replier); if (const auto original = top->lookupDiscussionPostOriginal()) { - changeFor(original); + original->changeRepliesCount(delta, replier); } } diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 439e7f0fe..5a23a25ba 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -142,10 +142,7 @@ public: void setForwardsCount(int count) override; void setReplies(HistoryMessageRepliesData &&data) override; void clearReplies() override; - void changeRepliesCount( - int delta, - PeerId replier, - std::optional unread) override; + void changeRepliesCount(int delta, PeerId replier) override; void setReplyFields( MsgId replyTo, MsgId replyToTop, @@ -198,17 +195,10 @@ public: [[nodiscard]] bool repliesAreComments() const override; [[nodiscard]] bool externalReply() const override; - [[nodiscard]] MsgId repliesInboxReadTill() const override; - void setRepliesInboxReadTill( - MsgId readTillId, - std::optional unreadCount) override; - [[nodiscard]] MsgId computeRepliesInboxReadTillFull() const override; - [[nodiscard]] MsgId repliesOutboxReadTill() const override; - void setRepliesOutboxReadTill(MsgId readTillId) override; - [[nodiscard]] MsgId computeRepliesOutboxReadTillFull() const override; - void setRepliesMaxId(MsgId maxId) override; - void setRepliesPossibleMaxId(MsgId possibleMaxId) override; - [[nodiscard]] bool areRepliesUnread() const override; + void setCommentsInboxReadTill(MsgId readTillId) override; + void setCommentsMaxId(MsgId maxId) override; + void setCommentsPossibleMaxId(MsgId possibleMaxId) override; + [[nodiscard]] bool areCommentsUnread() const override; [[nodiscard]] FullMsgId commentsItemId() const override; void setCommentsItemId(FullMsgId id) override; @@ -259,9 +249,6 @@ private: void refreshRepliesText( not_null views, bool forceResize = false); - void setUnreadRepliesCount( - not_null views, - int count); void setSponsoredFrom(const Data::SponsoredFrom &from); static void FillForwardedInfo( diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 094aa7867..1c3ff4faa 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1099,7 +1099,7 @@ void Message::paintCommentsButton( views ? views->replies.text : tr::lng_replies_view_original(tr::now), views ? views->replies.textWidth : -1); - if (views && data()->areRepliesUnread()) { + if (views && data()->areCommentsUnread()) { p.setPen(Qt::NoPen); p.setBrush(stm->msgFileBg); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 9788eb81c..ae661b81e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -129,7 +129,29 @@ RepliesMemento::RepliesMemento( commentId), .date = TimeId(0), }); - } else if (commentsItem->computeRepliesInboxReadTillFull() == MsgId(1)) { + } +} + +void RepliesMemento::setReadInformation( + MsgId inboxReadTillId, + int unreadCount, + MsgId outboxReadTillId) { + if (!_replies) { + if (const auto forum = _history->peer->forum()) { + if (const auto topic = forum->topicFor(_rootId)) { + _replies = topic->replies(); + } + } + if (!_replies) { + _replies = std::make_shared( + _history, + _rootId); + } + } + _replies->setInboxReadTill(inboxReadTillId, unreadCount); + _replies->setOutboxReadTill(outboxReadTillId); + if (!_list.aroundPosition().fullId + && _replies->computeInboxReadTillFull() == MsgId(1)) { _list.setAroundPosition(Data::MinMessagePosition); _list.setScrollTopState(ListMemento::ScrollTopState{ Data::MinMessagePosition @@ -303,24 +325,18 @@ RepliesWidget::RepliesWidget( } }, lifetime()); - using MessageUpdateFlag = Data::MessageUpdate::Flag; _history->session().changes().messageUpdates( - MessageUpdateFlag::Destroyed - | MessageUpdateFlag::RepliesUnreadCount + Data::MessageUpdate::Flag::Destroyed ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { - if (update.flags & MessageUpdateFlag::Destroyed) { - if (update.item == _root) { - _root = nullptr; - updatePinnedVisibility(); + if (update.item == _root) { + _root = nullptr; + updatePinnedVisibility(); + if (!_topic) { controller->showBackFromStack(); } - while (update.item == _replyReturn) { - calculateNextReplyReturn(); - } - return; - } else if ((update.item == _root) - && (update.flags & MessageUpdateFlag::RepliesUnreadCount)) { - refreshUnreadCountBadge(); + } + while (update.item == _replyReturn) { + calculateNextReplyReturn(); } }, lifetime()); @@ -331,17 +347,6 @@ RepliesWidget::RepliesWidget( _inner->update(); }, lifetime()); - _history->session().data().unreadRepliesCountRequests( - ) | rpl::filter([=]( - const Data::Session::UnreadRepliesCountRequest &request) { - return (request.root.get() == _root); - }) | rpl::start_with_next([=]( - const Data::Session::UnreadRepliesCountRequest &request) { - if (const auto result = computeUnreadCountLocally(request.afterId)) { - *request.result = result; - } - }, lifetime()); - setupScrollDownButton(); setupComposeControls(); orderWidgets(); @@ -374,21 +379,16 @@ void RepliesWidget::orderWidgets() { } void RepliesWidget::sendReadTillRequest() { - if (!_root) { - _readRequestPending = true; - return; - } if (_readRequestTimer.isActive()) { _readRequestTimer.cancel(); } - _readRequestPending = false; const auto api = &_history->session().api(); api->request(base::take(_readRequestId)).cancel(); _readRequestId = api->request(MTPmessages_ReadDiscussion( - _root->history()->peer->input, - MTP_int(_root->id), - MTP_int(_root->computeRepliesInboxReadTillFull()) + _history->peer->input, + MTP_int(_rootId), + MTP_int(_replies->computeInboxReadTillFull()) )).done(crl::guard(this, [=] { _readRequestId = 0; reloadUnreadCountIfNeeded(); @@ -401,10 +401,6 @@ void RepliesWidget::setupRoot() { _root = lookupRoot(); if (_root) { _areComments = computeAreComments(); - refreshUnreadCountBadge(); - if (_readRequestPending) { - sendReadTillRequest(); - } _inner->update(); } updatePinnedVisibility(); @@ -465,9 +461,10 @@ void RepliesWidget::setupTopicViewer() { if (_rootId == change.oldId) { _rootId = change.newId.msg; _root = lookupRoot(); - createReplies(); if (_topic && _topic->rootId() == change.oldId) { setTopic(_topic->forum()->topicFor(change.newId.msg)); + } else { + refreshReplies(); } _inner->update(); } @@ -475,7 +472,9 @@ void RepliesWidget::setupTopicViewer() { } void RepliesWidget::setTopic(Data::ForumTopic *topic) { - if ((_topic = topic)) { + if (_topic != topic) { + _topic = topic; + refreshReplies(); refreshTopBarActiveChat(); if (_topic && _rootView) { _rootView = nullptr; @@ -508,19 +507,6 @@ bool RepliesWidget::computeAreComments() const { return _root && _root->isDiscussionPost(); } -std::optional RepliesWidget::computeUnreadCount() const { - if (!_root) { - return std::nullopt; - } - const auto views = _root->Get(); - if (!views) { - return std::nullopt; - } - return (views->repliesUnreadCount >= 0) - ? std::make_optional(views->repliesUnreadCount) - : std::nullopt; -} - void RepliesWidget::setupComposeControls() { auto slowmodeSecondsLeft = session().changes().peerFlagsValue( _history->peer, @@ -1373,7 +1359,6 @@ void RepliesWidget::setupScrollDownButton() { _scrollDown->setClickedCallback([=] { scrollDownClicked(); }); - refreshUnreadCountBadge(); base::install_event_filter(_scrollDown, [=](not_null event) { if (event->type() != QEvent::Wheel) { return base::EventFilterResult::Continue; @@ -1385,53 +1370,22 @@ void RepliesWidget::setupScrollDownButton() { updateScrollDownVisibility(); } -void RepliesWidget::refreshUnreadCountBadge() { - if (!_root) { - return; - } else if (const auto count = computeUnreadCount()) { +void RepliesWidget::refreshUnreadCountBadge(std::optional count) { + if (count.has_value()) { _scrollDown->setUnreadCount(*count); - } else if (!_readRequestPending - && !_readRequestTimer.isActive() - && !_readRequestId) { + } else if (!_readRequestTimer.isActive() && !_readRequestId) { reloadUnreadCountIfNeeded(); } } void RepliesWidget::reloadUnreadCountIfNeeded() { - const auto views = _root ? _root->Get() : nullptr; - if (!views || views->repliesUnreadCount >= 0) { + if (_replies->unreadCountKnown()) { return; - } else if (views->repliesInboxReadTillId - < _root->computeRepliesInboxReadTillFull()) { + } else if (_replies->inboxReadTillId() + < _replies->computeInboxReadTillFull()) { _readRequestTimer.callOnce(0); - } else if (!_reloadUnreadCountRequestId) { - const auto session = &_history->session(); - const auto fullId = _root->fullId(); - const auto apply = [session, fullId](int readTill, int unreadCount) { - if (const auto root = session->data().message(fullId)) { - root->setRepliesInboxReadTill(readTill, unreadCount); - if (const auto post = root->lookupDiscussionPostOriginal()) { - post->setRepliesInboxReadTill(readTill, unreadCount); - } - } - }; - const auto weak = Ui::MakeWeak(this); - _reloadUnreadCountRequestId = session->api().request( - MTPmessages_GetDiscussionMessage( - _history->peer->input, - MTP_int(_rootId)) - ).done([=](const MTPmessages_DiscussionMessage &result) { - if (weak) { - _reloadUnreadCountRequestId = 0; - } - result.match([&](const MTPDmessages_discussionMessage &data) { - session->data().processUsers(data.vusers()); - session->data().processChats(data.vchats()); - apply( - data.vread_inbox_max_id().value_or_empty(), - data.vunread_count().v); - }); - }).send(); + } else { + _replies->requestUnreadCount(); } } @@ -1735,9 +1689,11 @@ void RepliesWidget::saveState(not_null memento) { _inner->saveState(memento->list()); } -void RepliesWidget::createReplies() { +void RepliesWidget::refreshReplies() { auto old = base::take(_replies); - setReplies(std::make_shared(_history, _rootId)); + setReplies(_topic + ? _topic->replies() + : std::make_shared(_history, _rootId)); if (old) { _inner->showAroundPosition(Data::UnreadMessagePosition, nullptr); } @@ -1746,6 +1702,16 @@ void RepliesWidget::createReplies() { void RepliesWidget::setReplies(std::shared_ptr replies) { _replies = std::move(replies); _repliesLifetime.destroy(); + + _replies->unreadCountValue( + ) | rpl::start_with_next([=](std::optional count) { + refreshUnreadCountBadge(count); + }, lifetime()); + + refreshUnreadCountBadge(_replies->unreadCountKnown() + ? _replies->unreadCountCurrent() + : std::optional()); + if (_topic) { return; } @@ -1772,7 +1738,7 @@ void RepliesWidget::restoreState(not_null memento) { if (auto replies = memento->getReplies()) { setReplies(std::move(replies)); } else if (!_replies) { - createReplies(); + refreshReplies(); } restoreReplyReturns(memento->replyReturns()); _inner->restoreState(memento->list()); @@ -2035,35 +2001,20 @@ void RepliesWidget::listSelectionChanged(SelectedItems &&items) { _topBar->showSelected(state); } -std::optional RepliesWidget::computeUnreadCountLocally( - MsgId afterId) const { - const auto views = _root ? _root->Get() : nullptr; - if (!views) { - return std::nullopt; - } - const auto wasReadTillId = views->repliesInboxReadTillId; - const auto wasUnreadCount = views->repliesUnreadCount; - return _replies->fullUnreadCountAfter( - afterId, - wasReadTillId, - wasUnreadCount); -} - void RepliesWidget::readTill(not_null item) { - if (!_root) { - return; - } - const auto was = _root->computeRepliesInboxReadTillFull(); + const auto was = _replies->computeInboxReadTillFull(); const auto now = item->id; if (now < was) { return; } - const auto unreadCount = computeUnreadCountLocally(now); + const auto unreadCount = _replies->computeUnreadCountLocally(now); const auto fast = item->out() || !unreadCount.has_value(); if (was < now || (fast && now == was)) { - _root->setRepliesInboxReadTill(now, unreadCount); - if (const auto post = _root->lookupDiscussionPostOriginal()) { - post->setRepliesInboxReadTill(now, unreadCount); + _replies->setInboxReadTill(now, unreadCount); + if (_root) { + if (const auto post = _root->lookupDiscussionPostOriginal()) { + post->setCommentsInboxReadTill(now); + } } if (!_readRequestTimer.isActive()) { _readRequestTimer.callOnce(fast ? 0 : kReadRequestTimeout); @@ -2083,10 +2034,10 @@ void RepliesWidget::listVisibleItemsChanged(HistoryItemsList &&items) { MessagesBarData RepliesWidget::listMessagesBar( const std::vector> &elements) { - if (!_root || elements.empty()) { + if (elements.empty()) { return {}; } - const auto till = _root->computeRepliesInboxReadTillFull(); + const auto till = _replies->computeInboxReadTillFull(); const auto hidden = (till < 2); for (auto i = 0, count = int(elements.size()); i != count; ++i) { const auto item = elements[i]->data(); @@ -2125,8 +2076,8 @@ bool RepliesWidget::listElementShownUnread(not_null view) { } const auto item = view->data(); const auto till = item->out() - ? _root->computeRepliesOutboxReadTillFull() - : _root->computeRepliesInboxReadTillFull(); + ? _replies->computeOutboxReadTillFull() + : _replies->computeInboxReadTillFull(); return (item->id > till); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 5d334ae9e..80843ff07 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -164,7 +164,7 @@ private: void saveState(not_null memento); void restoreState(not_null memento); void setReplies(std::shared_ptr replies); - void createReplies(); + void refreshReplies(); void showAtStart(); void showAtEnd(); void showAtPosition( @@ -185,8 +185,6 @@ private: void setupDragArea(); void sendReadTillRequest(); void readTill(not_null item); - [[nodiscard]] std::optional computeUnreadCountLocally( - MsgId afterId) const; void setupScrollDownButton(); void scrollDownClicked(); @@ -215,7 +213,6 @@ private: [[nodiscard]] HistoryItem *lookupRoot() const; [[nodiscard]] Data::ForumTopic *lookupTopic(); [[nodiscard]] bool computeAreComments() const; - [[nodiscard]] std::optional computeUnreadCount() const; void orderWidgets(); void pushReplyReturn(not_null item); @@ -226,7 +223,7 @@ private: void recountChatWidth(); void replyToMessage(FullMsgId itemId); void refreshTopBarActiveChat(); - void refreshUnreadCountBadge(); + void refreshUnreadCountBadge(std::optional count); void reloadUnreadCountIfNeeded(); void uploadFile(const QByteArray &fileContent, SendMediaType type); @@ -308,10 +305,8 @@ private: bool _choosingAttach = false; base::Timer _readRequestTimer; - bool _readRequestPending = false; mtpRequestId _readRequestId = 0; - mtpRequestId _reloadUnreadCountRequestId = 0; bool _loaded = false; }; @@ -331,6 +326,11 @@ public: not_null commentsItem, MsgId commentId = 0); + void setReadInformation( + MsgId inboxReadTillId, + int unreadCount, + MsgId outboxReadTillId); + object_ptr createWidget( QWidget *parent, not_null controller, diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 0aaf26335..7d3b75a0e 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include namespace Ui::BotWebView { namespace { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 3ec6ed905..da669e052 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -494,44 +494,48 @@ void SessionNavigation::showRepliesForMessage( data.vmessages(), NewMessageType::Existing); const auto list = data.vmessages().v; - if (list.isEmpty()) { + const auto deleted = list.isEmpty(); + const auto comments = history->peer->isBroadcast(); + if (comments && deleted) { return; } - const auto id = IdFromMessage(list.front()); - const auto peer = PeerFromMessage(list.front()); + const auto id = deleted ? rootId : IdFromMessage(list.front()); + const auto peer = deleted + ? history->peer->id + : PeerFromMessage(list.front()); if (!peer || !id) { return; } - auto item = _session->data().message(peer, id); - if (const auto group = _session->data().groups().find(item)) { + auto item = deleted + ? nullptr + : _session->data().message(peer, id); + if (comments && !item) { + return; + } + auto &groups = _session->data().groups(); + if (const auto group = item ? groups.find(item) : nullptr) { item = group->items.front(); } - if (item) { - if (const auto maxId = data.vmax_id()) { - item->setRepliesMaxId(maxId->v); - } - item->setRepliesInboxReadTill( - data.vread_inbox_max_id().value_or_empty(), - data.vunread_count().v); - item->setRepliesOutboxReadTill( - data.vread_outbox_max_id().value_or_empty()); + if (comments) { const auto post = _session->data().message(postPeer, rootId); - if (post && item->history()->peer != postPeer) { + if (post) { post->setCommentsItemId(item->fullId()); if (const auto maxId = data.vmax_id()) { - post->setRepliesMaxId(maxId->v); + post->setCommentsMaxId(maxId->v); } - post->setRepliesInboxReadTill( - data.vread_inbox_max_id().value_or_empty(), - data.vunread_count().v); - post->setRepliesOutboxReadTill( - data.vread_outbox_max_id().value_or_empty()); + post->setCommentsInboxReadTill( + data.vread_inbox_max_id().value_or_empty()); } - showSection( - std::make_shared( - item, - commentId), - params); + } + if (deleted || item) { + auto memento = std::make_shared( + item, + commentId); + memento->setReadInformation( + data.vread_inbox_max_id().value_or_empty(), + data.vunread_count().v, + data.vread_outbox_max_id().value_or_empty()); + showSection(std::move(memento), params); } }); }).fail([=](const MTP::Error &error) {