diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 47804bba96..ad0f7b7283 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -711,6 +711,7 @@ void Histories::sendReadRequest(not_null history, State &state) { } else { Assert(!state->sentReadTill || state->sentReadTill > tillId); } + history->validateMonoforumUnread(tillId); sendReadRequests(); finish(); }; diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp index ab3ac5f4eb..8108530c3b 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.cpp +++ b/Telegram/SourceFiles/data/data_saved_messages.cpp @@ -521,6 +521,27 @@ auto SavedMessages::recentSublists() const return _lastSublists; } +void SavedMessages::markUnreadCountsUnknown(MsgId readTillId) { + for (const auto &[peer, sublist] : _sublists) { + if (sublist->unreadCountCurrent() > 0) { + sublist->setInboxReadTill(readTillId, std::nullopt); + } + } +} + +void SavedMessages::updateUnreadCounts( + MsgId readTillId, + const base::flat_map, int> &counts) { + for (const auto &[peer, sublist] : _sublists) { + const auto raw = sublist.get(); + const auto i = counts.find(raw); + const auto count = (i != end(counts)) ? i->second : 0; + if (raw->unreadCountCurrent() != count) { + raw->setInboxReadTill(readTillId, count); + } + } +} + rpl::producer<> SavedMessages::destroyed() const { if (!_parentChat) { return rpl::never<>(); diff --git a/Telegram/SourceFiles/data/data_saved_messages.h b/Telegram/SourceFiles/data/data_saved_messages.h index c7568326c5..34800518b5 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.h +++ b/Telegram/SourceFiles/data/data_saved_messages.h @@ -70,6 +70,11 @@ public: [[nodiscard]] auto recentSublists() const -> const std::vector> &; + void markUnreadCountsUnknown(MsgId readTillId); + void updateUnreadCounts( + MsgId readTillId, + const base::flat_map, int> &counts); + void clear(); [[nodiscard]] rpl::lifetime &lifetime(); diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp index 594a1d1935..c8271b6824 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.cpp +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_saved_sublist.h" +#include "api/api_unread_things.h" #include "apiwrap.h" #include "core/application.h" #include "data/data_changes.h" @@ -61,7 +62,7 @@ SavedSublist::~SavedSublist() { if (_readRequestTimer.isActive()) { sendReadTillRequest(); } - // session().api().unreadThings().cancelRequests(this); + session().api().unreadThings().cancelRequests(this); } bool SavedSublist::inMonoforum() const { @@ -444,6 +445,9 @@ void SavedSublist::setInboxReadTill( && !_list.empty() && _inboxReadTillId >= _list.front()) { unreadCount = 0; + } else if (_lastServerMessage.value_or(nullptr) + && (*_lastServerMessage)->id <= newReadTillId) { + unreadCount = 0; } if (_unreadCount.current() != unreadCount && (changed || unreadCount.has_value())) { @@ -646,10 +650,11 @@ void SavedSublist::sendReadTillRequest() { const auto api = &_parent->session().api(); api->request(base::take(_readRequestId)).cancel(); + _sentReadTill = computeInboxReadTillFull(); _readRequestId = api->request(MTPmessages_ReadSavedHistory( parentChat->input, sublistPeer()->input, - MTP_int(computeInboxReadTillFull()) + MTP_int(_sentReadTill.bare) )).done(crl::guard(this, [=] { _readRequestId = 0; reloadUnreadCountIfNeeded(); @@ -708,6 +713,20 @@ void SavedSublist::applyMonoforumDialog( setInboxReadTill( data.vread_inbox_max_id().v, data.vunread_count().v); + if (!unreadCountKnown() && !_readRequestId) { + // We got read_inbox_max_id < than our current inboxReadTillId, + // we need either to send a read request with this new value, + // or to downgrade inboxReadTillId locally. + if (_sentReadTill < computeInboxReadTillFull()) { + sendReadTillRequest(); + } else { + // Just if nothing else helps. + _inboxReadTillId = 0; + setInboxReadTill( + data.vread_inbox_max_id().v, + data.vunread_count().v); + } + } setOutboxReadTill(data.vread_outbox_max_id().v); unreadReactions().setCount(data.vunread_reactions_count().v); setUnreadMark(data.is_unread_mark()); diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h index 095fdacf1e..1361f207fb 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.h +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -184,6 +184,7 @@ private: base::Timer _readRequestTimer; mtpRequestId _readRequestId = 0; + MsgId _sentReadTill = 0; mtpRequestId _reloadUnreadCountRequestId = 0; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 8061df17fb..b419b414b4 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -3108,8 +3108,70 @@ void History::applyDialogTopMessage(MsgId topMessageId) { } } +void History::tryMarkMonoforumIntervalRead( + MsgId wasInboxReadBefore, + MsgId nowInboxReadBefore) { + if (!amMonoforumAdmin() || (nowInboxReadBefore <= wasInboxReadBefore)) { + return; + } else if (loadedAtBottom() && nowInboxReadBefore >= minMsgId()) { + // Count for each sublist how many messages are still not read. + auto counts = base::flat_map, int>(); + for (const auto &block : blocks) { + for (const auto &message : block->messages) { + const auto item = message->data(); + if (!item->isRegular() || item->id < nowInboxReadBefore) { + continue; + } + if (const auto sublist = item->savedSublist()) { + ++counts[sublist]; + } + } + } + if (const auto monoforum = peer->monoforum()) { + monoforum->updateUnreadCounts(nowInboxReadBefore - 1, counts); + } + } else if (minMsgId() <= wasInboxReadBefore + && maxMsgId() >= nowInboxReadBefore) { + // Count for each sublist how many messages were read. + for (const auto &block : blocks) { + for (const auto &message : block->messages) { + const auto item = message->data(); + if (!item->isRegular() || item->id < wasInboxReadBefore) { + continue; + } else if (item->id >= nowInboxReadBefore) { + break; + } + if (const auto sublist = item->savedSublist()) { + const auto unread = sublist->unreadCountCurrent(); + if (unread > 0) { + sublist->setInboxReadTill(item->id, unread - 1); + } + } + } + } + } else { + // We can't invalidate sublist unread counts here, because no read + // request was yet sent to the server (so it can't return correct + // values yet), we need to do that after we send read request. + _flags |= Flag::MonoforumUnreadInvalidatePending; + } +} + +void History::validateMonoforumUnread(MsgId readTillId) { + if (!(_flags & Flag::MonoforumUnreadInvalidatePending)) { + return; + } + _flags &= ~Flag::MonoforumUnreadInvalidatePending; + if (!amMonoforumAdmin()) { + return; + } else if (const auto monoforum = peer->monoforum()) { + monoforum->markUnreadCountsUnknown(readTillId); + } +} + void History::setInboxReadTill(MsgId upTo) { if (_inboxReadBefore) { + tryMarkMonoforumIntervalRead(*_inboxReadBefore, upTo + 1); accumulate_max(*_inboxReadBefore, upTo + 1); } else { _inboxReadBefore = upTo + 1; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index cd6e707372..7ebacdfc82 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -430,6 +430,10 @@ public: // Interface for Data::Histories. void setInboxReadTill(MsgId upTo); std::optional countStillUnreadLocal(MsgId readTillId) const; + void tryMarkMonoforumIntervalRead( + MsgId wasInboxReadBefore, + MsgId nowInboxReadBefore); + void validateMonoforumUnread(MsgId readTillId); [[nodiscard]] bool isTopPromoted() const; @@ -466,7 +470,7 @@ public: private: friend class HistoryBlock; - enum class Flag : uchar { + enum class Flag : ushort { HasPendingResizedItems = (1 << 0), PendingAllItemsResize = (1 << 1), IsTopPromoted = (1 << 2), @@ -475,6 +479,7 @@ private: FakeUnreadWhileOpened = (1 << 5), HasPinnedMessages = (1 << 6), ResolveChatListMessage = (1 << 7), + MonoforumUnreadInvalidatePending = (1 << 8), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index f7367cba1b..2d883e5704 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -3632,10 +3632,11 @@ void HistoryWidget::unreadCountUpdated() { }); } else { const auto hideCounter = _history->isForum() - || _history->amMonoforumAdmin() || !_history->trackUnreadMessages(); _cornerButtons.updateJumpDownVisibility(hideCounter ? 0 + : _history->amMonoforumAdmin() + ? _history->chatListUnreadState().messages : _history->chatListBadgesState().unreadCounter); } } diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp index b69a8604b5..886044ed20 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp @@ -349,7 +349,6 @@ void SubsectionTabs::setupSlider( } } } - slider->setSections({ .tabs = std::move(sections), .context = Core::TextContext({ @@ -591,11 +590,24 @@ void SubsectionTabs::refreshSlice() { const auto push = [&](not_null thread) { const auto topic = thread->asTopic(); const auto sublist = thread->asSublist(); + const auto badges = [&] { + if (!topic && !sublist) { + return Dialogs::BadgesState(); + } else if (thread->chatListUnreadState().known) { + return thread->chatListBadgesState(); + } + const auto i = ranges::find(_slice, thread, &Item::thread); + if (i != end(_slice)) { + // While the unread count is unknown (possibly loading) + // we can preserve the old badges state, because it won't + // glitch that way when we stop knowing it for a moment. + return i->badges; + } + return thread->chatListBadgesState(); + }(); slice.push_back({ .thread = thread, - .badges = ((topic || sublist) - ? thread->chatListBadgesState() - : Dialogs::BadgesState()), + .badges = badges, .iconId = topic ? topic->iconId() : DocumentId(), .name = thread->chatListName(), });