Correctly mark monoforum chats as read.

This commit is contained in:
John Preston 2025-06-03 14:16:07 +04:00
parent 7f7b764f7b
commit f4582ddf36
9 changed files with 135 additions and 8 deletions

View file

@ -711,6 +711,7 @@ void Histories::sendReadRequest(not_null<History*> history, State &state) {
} else {
Assert(!state->sentReadTill || state->sentReadTill > tillId);
}
history->validateMonoforumUnread(tillId);
sendReadRequests();
finish();
};

View file

@ -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<not_null<SavedSublist*>, 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<>();

View file

@ -70,6 +70,11 @@ public:
[[nodiscard]] auto recentSublists() const
-> const std::vector<not_null<SavedSublist*>> &;
void markUnreadCountsUnknown(MsgId readTillId);
void updateUnreadCounts(
MsgId readTillId,
const base::flat_map<not_null<SavedSublist*>, int> &counts);
void clear();
[[nodiscard]] rpl::lifetime &lifetime();

View file

@ -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());

View file

@ -184,6 +184,7 @@ private:
base::Timer _readRequestTimer;
mtpRequestId _readRequestId = 0;
MsgId _sentReadTill = 0;
mtpRequestId _reloadUnreadCountRequestId = 0;

View file

@ -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<not_null<Data::SavedSublist*>, 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;

View file

@ -430,6 +430,10 @@ public:
// Interface for Data::Histories.
void setInboxReadTill(MsgId upTo);
std::optional<int> 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<Flag>;
friend inline constexpr auto is_flag_type(Flag) {

View file

@ -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);
}
}

View file

@ -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<Data::Thread*> 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(),
});