Track unread posts in forums inside RepliesList-s.

This commit is contained in:
John Preston 2022-10-07 15:46:27 +04:00
parent 0d985b5745
commit 9348039313
18 changed files with 567 additions and 582 deletions

View file

@ -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: {

View file

@ -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),

View file

@ -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*> history, MsgId rootId)
: Entry(&history->owner(), Type::ForumTopic)
, _history(history)
, _list(forum()->topicsList())
, _replies(std::make_shared<RepliesList>(history, rootId))
, _rootId(rootId) {
_replies->unreadCountValue(
) | rpl::combine_previous(
) | rpl::filter([=] {
return inChatList();
}) | rpl::start_with_next([=](
std::optional<int> previous,
std::optional<int> now) {
notifyUnreadStateChange(unreadStateFor(
previous.value_or(0),
previous.has_value()));
}, _replies->lifetime());
}
ForumTopic::~ForumTopic() = default;
std::shared_ptr<Data::RepliesList> ForumTopic::replies() const {
return _replies;
}
not_null<ChannelData*> 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<RepliesList>(_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;
}

View file

@ -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<RepliesList> replies() const;
[[nodiscard]] not_null<ChannelData*> channel() const;
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] not_null<Forum*> 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*> _history;
const not_null<Dialogs::MainList*> _list;
std::shared_ptr<RepliesList> _replies;
MsgId _rootId = 0;
QString _title;
@ -141,9 +140,6 @@ private:
std::unique_ptr<Ui::Text::CustomEmoji> _icon;
mutable QImage _defaultIcon; // on-demand
std::optional<MsgId> _inboxReadBefore;
std::optional<MsgId> _outboxReadBefore;
std::optional<int> _unreadCount;
std::optional<HistoryItem*> _lastMessage;
std::optional<HistoryItem*> _lastServerMessage;
std::optional<HistoryItem*> _chatListMessage;

View file

@ -60,6 +60,33 @@ RepliesList::RepliesList(not_null<History*> 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<ChannelData*> channel) {
return applyDifferenceTooLong(channel);
}) | rpl::to_empty | rpl::start_to_stream(_listChanges, _lifetime);
}
RepliesList::~RepliesList() {
@ -95,33 +122,20 @@ rpl::producer<MessagesSlice> 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<ChannelData*> 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<int> RepliesList::fullCount() const {
return _fullCount.value() | rpl::filter_optional();
}
std::optional<int> RepliesList::fullUnreadCountAfter(
MsgId readTillId,
MsgId wasReadTillId,
std::optional<int> 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<std::optional<int>> RepliesList::unreadCountValue() const {
return _unreadCount.value();
}
void RepliesList::injectRootMessageAndReverse(not_null<Viewer*> viewer) {
@ -321,7 +289,7 @@ bool RepliesList::buildFromData(not_null<Viewer*> 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*> viewer) {
return true;
}
bool RepliesList::applyUpdate(
bool RepliesList::applyItemDestroyed(
not_null<Viewer*> viewer,
const MessageUpdate &update) {
if (update.item->history() != _history || !update.item->isRegular()) {
not_null<HistoryItem*> 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<ChannelData*> 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<int> 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<int> count) {
_unreadCount = count;
}
void RepliesList::checkReadTillEnd() {
if (_unreadCount.current() != 0
&& _skippedAfter == 0
&& !_list.empty()
&& _inboxReadTillId >= _list.front()) {
setUnreadCount(0);
}
}
std::optional<int> 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

View file

@ -31,10 +31,24 @@ public:
[[nodiscard]] rpl::producer<int> fullCount() const;
[[nodiscard]] std::optional<int> fullUnreadCountAfter(
MsgId readTillId,
MsgId wasReadTillId,
std::optional<int> wasUnreadCountAfter) const;
[[nodiscard]] bool unreadCountKnown() const;
[[nodiscard]] int unreadCountCurrent() const;
[[nodiscard]] rpl::producer<std::optional<int>> unreadCountValue() const;
void setInboxReadTill(MsgId readTillId, std::optional<int> unreadCount);
[[nodiscard]] MsgId inboxReadTillId() const;
[[nodiscard]] MsgId computeInboxReadTillFull() const;
void setOutboxReadTill(MsgId readTillId);
[[nodiscard]] MsgId computeOutboxReadTillFull() const;
[[nodiscard]] std::optional<int> 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*> viewer);
[[nodiscard]] bool applyUpdate(
[[nodiscard]] bool applyItemDestroyed(
not_null<Viewer*> viewer,
const MessageUpdate &update);
not_null<HistoryItem*> item);
[[nodiscard]] bool applyUpdate(const MessageUpdate &update);
[[nodiscard]] bool applyDifferenceTooLong(
not_null<ChannelData*> channel);
void injectRootMessageAndReverse(not_null<Viewer*> viewer);
void injectRootMessage(not_null<Viewer*> viewer);
void injectRootDivider(
@ -62,20 +79,32 @@ private:
void loadBefore();
void loadAfter();
void changeUnreadCountByPost(MsgId id, int delta);
void setUnreadCount(std::optional<int> count);
void checkReadTillEnd();
const not_null<History*> _history;
const MsgId _rootId = 0;
const bool _creating = false;
std::vector<MsgId> _list;
std::optional<int> _skippedBefore;
std::optional<int> _skippedAfter;
rpl::variable<std::optional<int>> _fullCount;
rpl::event_stream<> _partLoaded;
rpl::event_stream<> _listChanges;
std::optional<MsgId> _loadingAround;
rpl::variable<std::optional<int>> _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

View file

@ -2364,21 +2364,13 @@ void Session::notifyUnreadBadgeChanged() {
_unreadBadgeChanges.fire({});
}
std::optional<int> Session::countUnreadRepliesLocally(
not_null<HistoryItem*> root,
MsgId afterId) const {
auto result = std::optional<int>();
_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<UnreadRepliesCountRequest> {
return _unreadRepliesCountRequests.events();
auto Session::repliesReadTillUpdates() const
-> rpl::producer<RepliesReadTillUpdate> {
return _repliesReadTillUpdates.events();
}
int Session::computeUnreadBadge(const Dialogs::UnreadState &state) const {
@ -3787,6 +3779,14 @@ not_null<Folder*> Session::processFolder(const MTPDfolder &data) {
return folder(data.vid().v);
}
not_null<Dialogs::MainList*> Session::chatsListFor(
not_null<Dialogs::Entry*> entry) {
const auto topic = entry->asTopic();
return topic
? topic->forum()->topicsList()
: chatsList(entry->folder());
}
not_null<Dialogs::MainList*> 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,

View file

@ -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<int> countUnreadRepliesLocally(
not_null<HistoryItem*> root,
MsgId afterId) const;
struct UnreadRepliesCountRequest {
not_null<HistoryItem*> root;
MsgId afterId = 0;
not_null<std::optional<int>*> result;
};
[[nodiscard]] auto unreadRepliesCountRequests() const
-> rpl::producer<UnreadRepliesCountRequest>;
void updateRepliesReadTill(RepliesReadTillUpdate update);
[[nodiscard]] auto repliesReadTillUpdates() const
-> rpl::producer<RepliesReadTillUpdate>;
void selfDestructIn(not_null<HistoryItem*> item, crl::time delay);
@ -647,6 +646,8 @@ public:
not_null<Folder*> processFolder(const MTPFolder &data);
not_null<Folder*> processFolder(const MTPDfolder &data);
[[nodiscard]] not_null<Dialogs::MainList*> chatsListFor(
not_null<Dialogs::Entry*> entry);
[[nodiscard]] not_null<Dialogs::MainList*> chatsList(
Data::Folder *folder = nullptr);
[[nodiscard]] not_null<const Dialogs::MainList*> chatsList(
@ -863,7 +864,7 @@ private:
rpl::event_stream<DialogsRowReplacement> _dialogsRowReplacements;
rpl::event_stream<ChatListEntryRefresh> _chatListEntryRefreshes;
rpl::event_stream<> _unreadBadgeChanges;
rpl::event_stream<UnreadRepliesCountRequest> _unreadRepliesCountRequests;
rpl::event_stream<RepliesReadTillUpdate> _repliesReadTillUpdates;
Dialogs::MainList _chatsList;
Dialogs::IndexedList _contactsList;

View file

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

View file

@ -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<int> 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<bool> unread) {
virtual void changeRepliesCount(int delta, PeerId replier) {
}
virtual void setReplyFields(
MsgId replyTo,

View file

@ -58,12 +58,10 @@ struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, Histor
Part views;
Part replies;
Part repliesSmall;
MsgId repliesInboxReadTillId = 0;
MsgId repliesOutboxReadTillId = 0;
MsgId repliesMaxId = 0;
int repliesUnreadCount = -1; // unknown
ChannelId commentsMegagroupId = 0;
MsgId commentsRootId = 0;
MsgId commentsInboxReadTillId = 0;
MsgId commentsMaxId = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> {

View file

@ -721,137 +721,67 @@ bool HistoryMessage::externalReply() const {
return false;
}
MsgId HistoryMessage::repliesInboxReadTill() const {
if (const auto views = Get<HistoryMessageViews>()) {
return views->repliesInboxReadTillId;
}
return 0;
}
void HistoryMessage::setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) {
if (const auto views = Get<HistoryMessageViews>()) {
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<HistoryMessageViews>();
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<HistoryMessageViews>()) {
return views->repliesOutboxReadTillId;
}
return 0;
}
void HistoryMessage::setRepliesOutboxReadTill(MsgId readTillId) {
if (const auto views = Get<HistoryMessageViews>()) {
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<HistoryMessageViews>();
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<HistoryMessageViews>()) {
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<HistoryMessageViews>()) {
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<HistoryMessageViews>();
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<bool> unread) {
void HistoryMessage::changeRepliesCount(int delta, PeerId replier) {
const auto views = Get<HistoryMessageViews>();
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<HistoryMessageViews*> 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<HistoryMessageSponsored>();
@ -1866,37 +1771,27 @@ void HistoryMessage::incrementReplyToTopCounter() {
void HistoryMessage::changeReplyToTopCounter(
not_null<HistoryMessageReply*> 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<HistoryMessageViews>()) {
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<HistoryItem*> 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);
}
}

View file

@ -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<bool> 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<int> 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<HistoryMessageViews*> views,
bool forceResize = false);
void setUnreadRepliesCount(
not_null<HistoryMessageViews*> views,
int count);
void setSponsoredFrom(const Data::SponsoredFrom &from);
static void FillForwardedInfo(

View file

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

View file

@ -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<Data::RepliesList>(
_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<int> RepliesWidget::computeUnreadCount() const {
if (!_root) {
return std::nullopt;
}
const auto views = _root->Get<HistoryMessageViews>();
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<QEvent*> 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<int> 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<HistoryMessageViews>() : 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<RepliesMemento*> memento) {
_inner->saveState(memento->list());
}
void RepliesWidget::createReplies() {
void RepliesWidget::refreshReplies() {
auto old = base::take(_replies);
setReplies(std::make_shared<Data::RepliesList>(_history, _rootId));
setReplies(_topic
? _topic->replies()
: std::make_shared<Data::RepliesList>(_history, _rootId));
if (old) {
_inner->showAroundPosition(Data::UnreadMessagePosition, nullptr);
}
@ -1746,6 +1702,16 @@ void RepliesWidget::createReplies() {
void RepliesWidget::setReplies(std::shared_ptr<Data::RepliesList> replies) {
_replies = std::move(replies);
_repliesLifetime.destroy();
_replies->unreadCountValue(
) | rpl::start_with_next([=](std::optional<int> count) {
refreshUnreadCountBadge(count);
}, lifetime());
refreshUnreadCountBadge(_replies->unreadCountKnown()
? _replies->unreadCountCurrent()
: std::optional<int>());
if (_topic) {
return;
}
@ -1772,7 +1738,7 @@ void RepliesWidget::restoreState(not_null<RepliesMemento*> 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<int> RepliesWidget::computeUnreadCountLocally(
MsgId afterId) const {
const auto views = _root ? _root->Get<HistoryMessageViews>() : 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<HistoryItem*> 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<not_null<Element*>> &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<const Element*> view) {
}
const auto item = view->data();
const auto till = item->out()
? _root->computeRepliesOutboxReadTillFull()
: _root->computeRepliesInboxReadTillFull();
? _replies->computeOutboxReadTillFull()
: _replies->computeInboxReadTillFull();
return (item->id > till);
}

View file

@ -164,7 +164,7 @@ private:
void saveState(not_null<RepliesMemento*> memento);
void restoreState(not_null<RepliesMemento*> memento);
void setReplies(std::shared_ptr<Data::RepliesList> replies);
void createReplies();
void refreshReplies();
void showAtStart();
void showAtEnd();
void showAtPosition(
@ -185,8 +185,6 @@ private:
void setupDragArea();
void sendReadTillRequest();
void readTill(not_null<HistoryItem*> item);
[[nodiscard]] std::optional<int> 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<int> computeUnreadCount() const;
void orderWidgets();
void pushReplyReturn(not_null<HistoryItem*> item);
@ -226,7 +223,7 @@ private:
void recountChatWidth();
void replyToMessage(FullMsgId itemId);
void refreshTopBarActiveChat();
void refreshUnreadCountBadge();
void refreshUnreadCountBadge(std::optional<int> 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<HistoryItem*> commentsItem,
MsgId commentId = 0);
void setReadInformation(
MsgId inboxReadTillId,
int unreadCount,
MsgId outboxReadTillId);
object_ptr<Window::SectionWidget> createWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,

View file

@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtGui/QDesktopServices>
#include <QtWidgets/QApplication>
namespace Ui::BotWebView {
namespace {

View file

@ -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<HistoryView::RepliesMemento>(
item,
commentId),
params);
}
if (deleted || item) {
auto memento = std::make_shared<HistoryView::RepliesMemento>(
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) {