mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Track unread posts in forums inside RepliesList-s.
This commit is contained in:
parent
0d985b5745
commit
9348039313
18 changed files with 567 additions and 582 deletions
|
@ -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: {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue